From c7cd5d5fe9ef366c6fa4c7cea313fc141ed892a0 Mon Sep 17 00:00:00 2001 From: Robin Dunn Date: Fri, 26 Apr 2013 07:04:14 +0000 Subject: [PATCH] Add the FloatCanvas demo modules git-svn-id: https://svn.wxwidgets.org/svn/wx/wxPython/Phoenix/trunk@73860 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- samples/floatcanvas/Animation.py | 304 ++++++++++++++++++ samples/floatcanvas/BB_HitTest.py | 133 ++++++++ samples/floatcanvas/BNAEditor.py | 353 +++++++++++++++++++++ samples/floatcanvas/BarPlot.py | 149 +++++++++ samples/floatcanvas/BouncingBall.py | 212 +++++++++++++ samples/floatcanvas/Chart.py | 126 ++++++++ samples/floatcanvas/ClickableBoxes.py | 119 +++++++ samples/floatcanvas/DrawBot.py | 72 +++++ samples/floatcanvas/DrawRect.py | 117 +++++++ samples/floatcanvas/GridDemo.py | 84 +++++ samples/floatcanvas/GroupDeleteDemo.py | 121 +++++++ samples/floatcanvas/GroupDemo.py | 111 +++++++ samples/floatcanvas/Hexagons.py | 102 ++++++ samples/floatcanvas/Map.py | 77 +++++ samples/floatcanvas/MicroDemo.py | 83 +++++ samples/floatcanvas/MiniDemo.py | 381 +++++++++++++++++++++++ samples/floatcanvas/MouseTest.py | 48 +++ samples/floatcanvas/MovingElements.py | 281 +++++++++++++++++ samples/floatcanvas/MovingPlot.py | 238 ++++++++++++++ samples/floatcanvas/MovingTriangle.py | 189 +++++++++++ samples/floatcanvas/NOAA.png | Bin 0 -> 5510 bytes samples/floatcanvas/NoToolbar.py | 93 ++++++ samples/floatcanvas/NonGUI.py | 43 +++ samples/floatcanvas/OverlayDemo.py | 142 +++++++++ samples/floatcanvas/PieChart.py | 91 ++++++ samples/floatcanvas/PixelBitmap.py | 150 +++++++++ samples/floatcanvas/PointsHitDemo.py | 81 +++++ samples/floatcanvas/PolyEditor.py | 255 +++++++++++++++ samples/floatcanvas/ProcessDiagram.py | 379 ++++++++++++++++++++++ samples/floatcanvas/ScaleDemo.py | 100 ++++++ samples/floatcanvas/ScaledBitmap2Demo.py | 83 +++++ samples/floatcanvas/SplitterWindow.py | 72 +++++ samples/floatcanvas/SubClassNavCanvas.py | 67 ++++ samples/floatcanvas/TestSpline.py | 185 +++++++++++ samples/floatcanvas/TextBox.py | 302 ++++++++++++++++++ samples/floatcanvas/TextBox2.py | 228 ++++++++++++++ samples/floatcanvas/Tiny.bna | 38 +++ samples/floatcanvas/Tree.py | 371 ++++++++++++++++++++++ samples/floatcanvas/VectPlot.py | 203 ++++++++++++ samples/floatcanvas/YDownDemo.py | 95 ++++++ samples/floatcanvas/white_tank.jpg | Bin 0 -> 166242 bytes 41 files changed, 6278 insertions(+) create mode 100755 samples/floatcanvas/Animation.py create mode 100755 samples/floatcanvas/BB_HitTest.py create mode 100755 samples/floatcanvas/BNAEditor.py create mode 100755 samples/floatcanvas/BarPlot.py create mode 100755 samples/floatcanvas/BouncingBall.py create mode 100755 samples/floatcanvas/Chart.py create mode 100755 samples/floatcanvas/ClickableBoxes.py create mode 100755 samples/floatcanvas/DrawBot.py create mode 100755 samples/floatcanvas/DrawRect.py create mode 100755 samples/floatcanvas/GridDemo.py create mode 100644 samples/floatcanvas/GroupDeleteDemo.py create mode 100644 samples/floatcanvas/GroupDemo.py create mode 100755 samples/floatcanvas/Hexagons.py create mode 100755 samples/floatcanvas/Map.py create mode 100755 samples/floatcanvas/MicroDemo.py create mode 100755 samples/floatcanvas/MiniDemo.py create mode 100755 samples/floatcanvas/MouseTest.py create mode 100755 samples/floatcanvas/MovingElements.py create mode 100755 samples/floatcanvas/MovingPlot.py create mode 100755 samples/floatcanvas/MovingTriangle.py create mode 100644 samples/floatcanvas/NOAA.png create mode 100755 samples/floatcanvas/NoToolbar.py create mode 100755 samples/floatcanvas/NonGUI.py create mode 100755 samples/floatcanvas/OverlayDemo.py create mode 100755 samples/floatcanvas/PieChart.py create mode 100755 samples/floatcanvas/PixelBitmap.py create mode 100755 samples/floatcanvas/PointsHitDemo.py create mode 100755 samples/floatcanvas/PolyEditor.py create mode 100644 samples/floatcanvas/ProcessDiagram.py create mode 100644 samples/floatcanvas/ScaleDemo.py create mode 100755 samples/floatcanvas/ScaledBitmap2Demo.py create mode 100644 samples/floatcanvas/SplitterWindow.py create mode 100755 samples/floatcanvas/SubClassNavCanvas.py create mode 100755 samples/floatcanvas/TestSpline.py create mode 100755 samples/floatcanvas/TextBox.py create mode 100755 samples/floatcanvas/TextBox2.py create mode 100644 samples/floatcanvas/Tiny.bna create mode 100755 samples/floatcanvas/Tree.py create mode 100755 samples/floatcanvas/VectPlot.py create mode 100755 samples/floatcanvas/YDownDemo.py create mode 100644 samples/floatcanvas/white_tank.jpg diff --git a/samples/floatcanvas/Animation.py b/samples/floatcanvas/Animation.py new file mode 100755 index 00000000..e5d0ae34 --- /dev/null +++ b/samples/floatcanvas/Animation.py @@ -0,0 +1,304 @@ +#!/usr/bin/env python + +""" +A test of some simple animation + +this is very old-style code: don't imitate it! + +""" + +from time import clock +import wx +from numpy import * + +## import local version: +import sys + +#ver = 'local' +ver = 'installed' + +if ver == 'installed': ## import the installed version + from wx.lib.floatcanvas import NavCanvas + from wx.lib.floatcanvas import FloatCanvas + print "using installed version:", wx.lib.floatcanvas.__version__ +elif ver == 'local': + ## import a local version + import sys + sys.path.append("..") + from floatcanvas import NavCanvas + from floatcanvas import FloatCanvas + +ID_DRAW_BUTTON = 100 +ID_QUIT_BUTTON = 101 +ID_CLEAR_BUTTON = 103 +ID_ZOOM_IN_BUTTON = 104 +ID_ZOOM_OUT_BUTTON = 105 +ID_ZOOM_TO_FIT_BUTTON = 110 +ID_MOVE_MODE_BUTTON = 111 +ID_TEST_BUTTON = 112 + +ID_ABOUT_MENU = 200 +ID_EXIT_MENU = 201 +ID_ZOOM_IN_MENU = 202 +ID_ZOOM_OUT_MENU = 203 +ID_ZOOM_TO_FIT_MENU = 204 +ID_DRAWTEST_MENU = 205 +ID_DRAWMAP_MENU = 206 +ID_CLEAR_MENU = 207 + + +ID_TEST = 500 + + +class DrawFrame(wx.Frame): + def __init__(self,parent, id,title,position,size): + wx.Frame.__init__(self,parent, id,title,position, size) + + ## Set up the MenuBar + + MenuBar = wx.MenuBar() + + file_menu = wx.Menu() + file_menu.Append(ID_EXIT_MENU, "E&xit","Terminate the program") + wx.EVT_MENU(self, ID_EXIT_MENU, self.OnQuit) + MenuBar.Append(file_menu, "&File") + + draw_menu = wx.Menu() + draw_menu.Append(ID_DRAWTEST_MENU, "&Draw Test","Run a test of drawing random components") + wx.EVT_MENU(self, ID_DRAWTEST_MENU,self.DrawTest) + draw_menu.Append(ID_DRAWMAP_MENU, "Draw &Movie","Run a test of drawing a map") + wx.EVT_MENU(self, ID_DRAWMAP_MENU,self.RunMovie) + draw_menu.Append(ID_CLEAR_MENU, "&Clear","Clear the Canvas") + wx.EVT_MENU(self, ID_CLEAR_MENU,self.Clear) + MenuBar.Append(draw_menu, "&Draw") + + + view_menu = wx.Menu() + view_menu.Append(ID_ZOOM_TO_FIT_MENU, "Zoom to &Fit","Zoom to fit the window") + wx.EVT_MENU(self, ID_ZOOM_TO_FIT_MENU,self.ZoomToFit) + MenuBar.Append(view_menu, "&View") + + help_menu = wx.Menu() + help_menu.Append(ID_ABOUT_MENU, "&About", + "More information About this program") + wx.EVT_MENU(self, ID_ABOUT_MENU, self.OnAbout) + MenuBar.Append(help_menu, "&Help") + + self.SetMenuBar(MenuBar) + + self.CreateStatusBar() + self.SetStatusText("") + + wx.EVT_CLOSE(self, self.OnCloseWindow) + + # Other event handlers: + wx.EVT_RIGHT_DOWN(self, self.RightButtonEvent) + + # Add the Canvas + self.Canvas = NavCanvas.NavCanvas(self,-1,(500,500), + Debug = False, + BackgroundColor = "WHITE").Canvas + self.Canvas.NumBetweenBlits = 1000 + self.Show(True) + + self.DrawTest(None) + 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 = wx.MessageDialog(self, "This is a small program to demonstrate\n" + "the use of the FloatCanvas\n", + "About Me", wx.OK | wx.ICON_INFORMATION) + dlg.ShowModal() + dlg.Destroy() + + def ZoomToFit(self,event): + self.Canvas.ZoomToBB() + + def Clear(self,event = None): + self.Canvas.ClearAll() + self.Canvas.Draw() + + def OnQuit(self,event): + self.Close(True) + + def OnCloseWindow(self, event): + self.Destroy() + + def DrawTest(self,event = None): + import random + import numpy.random as RandomArray + + Range = (-10,10) + + colors = ["AQUAMARINE", "BLACK", "BLUE", "BLUE VIOLET", "BROWN", + "CADET BLUE", "CORAL", "CORNFLOWER BLUE", "CYAN", "DARK GREY", + "DARK GREEN", "DARK OLIVE GREEN", "DARK ORCHID", "DARK SLATE BLUE", + "DARK SLATE GREY", "DARK TURQUOISE", "DIM GREY", + "FIREBRICK", "FOREST GREEN", "GOLD", "GOLDENROD", "GREY", + "GREEN", "GREEN YELLOW", "INDIAN RED", "KHAKI", "LIGHT BLUE", + "LIGHT GREY", "LIGHT STEEL BLUE", "LIME GREEN", "MAGENTA", + "MAROON", "MEDIUM AQUAMARINE", "MEDIUM BLUE", "MEDIUM FOREST GREEN", + "MEDIUM GOLDENROD", "MEDIUM ORCHID", "MEDIUM SEA GREEN", + "MEDIUM SLATE BLUE", "MEDIUM SPRING GREEN", "MEDIUM TURQUOISE", + "MEDIUM VIOLET RED", "MIDNIGHT BLUE", "NAVY", "ORANGE", "ORANGE RED", + "ORCHID", "PALE GREEN", "PINK", "PLUM", "PURPLE", "RED", + "SALMON", "SEA GREEN", "SIENNA", "SKY BLUE", "SLATE BLUE", + "SPRING GREEN", "STEEL BLUE", "TAN", "THISTLE", "TURQUOISE", + "VIOLET", "VIOLET RED", "WHEAT", "WHITE", "YELLOW", "YELLOW GREEN"] + Canvas = self.Canvas + + # Some Polygons in the background: +# for i in range(500): +# points = RandomArray.uniform(-100,100,(10,2)) + for i in range(500): +# for i in range(1): + points = RandomArray.uniform(-100,100,(10,2)) + lw = random.randint(1,6) + cf = random.randint(0,len(colors)-1) + cl = random.randint(0,len(colors)-1) + self.Canvas.AddPolygon(points, + LineWidth = lw, + LineColor = colors[cl], + FillColor = colors[cf], + FillStyle = 'Solid', + InForeground = False) + + ## Pointset + print "Adding Points to Foreground" + for i in range(1): + points = RandomArray.uniform(-100,100,(1000,2)) + D = 2 + self.LEs = self.Canvas.AddPointSet(points, Color = "Black", Diameter = D, InForeground = True) + + self.Canvas.AddRectangle((-200,-200), (400,400)) + Canvas.ZoomToBB() + + def RunMovie(self,event = None): + import numpy.random as RandomArray + start = clock() + #shift = RandomArray.randint(0,0,(2,)) + for i in range(100): + points = self.LEs.Points + shift = RandomArray.randint(-5,6,(2,)) + points += shift + self.LEs.SetPoints(points) + self.Canvas.Draw() + wx.GetApp().Yield(True) + print "running the movie took %f seconds"%(clock() - start) + +class DemoApp(wx.App): + """ + 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 + wx.Cursors didn't include anything I liked, so I stuck with the + pointer. Pleae let me know if you have any nice cursor images for me to + use. + + + Any bugs, comments, feedback, questions, and especially code are welcome: + + -Chris Barker + + ChrisHBarker@home.net + http://members.home.net/barkerlohmann + + """ + + def OnInit(self): + wx.InitAllImageHandlers() + frame = DrawFrame(None, -1, "Simple Drawing Window",wx.DefaultPosition, (700,700) ) + + self.SetTopWindow(frame) + + return True + +def Read_MapGen(filename,stats = False): + """ + 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 worls, in MapGen format. + + """ + import string + from numpy import array + file = open(filename,'rt') + data = file.readlines() + data = map(string.strip,data) + + Shorelines = [] + segment = [] + for line in data: + if line == "# -b": #New segment begining + 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 = False + for segment in Shorelines: + NumPoints = NumPoints + len(segment) + AvgPoints = NumPoints / NumSegments + print "Number of Segments: ", NumSegments + print "Average Number of Points per segment: ",AvgPoints + + return Shorelines + +if __name__ == "__main__": + + app = DemoApp(0) + app.MainLoop() + + + + + + + + + + diff --git a/samples/floatcanvas/BB_HitTest.py b/samples/floatcanvas/BB_HitTest.py new file mode 100755 index 00000000..29574317 --- /dev/null +++ b/samples/floatcanvas/BB_HitTest.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python +""" +Test of an alternaive hit test methoid that used the bounding boxes of teh objects instead. + +Poorly tested! + +Edited from code contributed by Benjamin Jessup on the mailing list + +""" + +import wx + +## import the installed version +from wx.lib.floatcanvas import NavCanvas, FloatCanvas + +## import a local version +#import sys +#sys.path.append("../") +#from floatcanvas import NavCanvas, FloatCanvas +FC = FloatCanvas + +def BB_HitTest(self, event, HitEvent): + """ Hit Test Function for BoundingBox Based HitMap System""" + if self.HitDict and self.HitDict[HitEvent]: + # loop though the objects associated with this event + objects = [] #Create object list for holding multiple objects + object_index_list = [] #Create list for holding the indexes + xy_p = event.GetPosition() + xy = self.PixelToWorld( xy_p ) #Convert to the correct coords + for key2 in self.HitDict[HitEvent].keys(): + #Get Mouse Event Position + bb = self.HitDict[HitEvent][key2].BoundingBox + if bb.PointInside(xy): + Object = self.HitDict[HitEvent][key2] + objects.append(Object) + try: + #First try the foreground index and add the length of the background index + #to account for the two 'layers' that already exist in the code + index = self._ForeDrawList.index(Object) + len(self._DrawList) + except ValueError: + index = self._DrawList.index(Object) #Now check background if not found in foreground + object_index_list.append(index) #append the index found + else: + Object = self.HitDict[HitEvent][key2] + if len(objects) > 0: #If no objects then do nothing + #Get the highest index object + highest_object = objects[object_index_list.index(max(object_index_list))] + highest_object.HitCoords = xy + highest_object.HitCoordsPixel = xy_p + highest_object.CallBackFuncs[HitEvent](highest_object) + return True + else: + return False + return False + +FC.FloatCanvas.HitTest = BB_HitTest + +class DrawFrame(wx.Frame): + + """ + A frame used for the FloatCanvas Demo + + """ + + def __init__(self, *args, **kwargs): + wx.Frame.__init__(self, *args, **kwargs) + + self.CreateStatusBar() + + # Add the Canvas + Canvas = NavCanvas.NavCanvas(self,-1, + size = (500,500), + ProjectionFun = None, + Debug = 0, + BackgroundColor = "DARK SLATE BLUE", + ).Canvas + + self.Canvas = Canvas + + FloatCanvas.EVT_MOTION(self.Canvas, self.OnMove ) + + Point = (45,40) + Text = Canvas.AddScaledText("A String", + Point, + 20, + Color = "Black", + BackgroundColor = None, + Family = wx.ROMAN, + Style = wx.NORMAL, + Weight = wx.NORMAL, + Underlined = False, + Position = 'bl', + InForeground = False) + Text.MinFontSize = 4 # the default is 1 + Text.DisappearWhenSmall = False #the default is True + + Rect1 = Canvas.AddRectangle((50, 20), (40,15), FillColor="Red", LineStyle = None) + Rect1.Bind(FC.EVT_FC_LEFT_DOWN, self.OnLeft) + Rect1.Name = "red" + + Rect2 = Canvas.AddRectangle((70, 30), (40,15), FillColor="Blue", LineStyle = None) + Rect2.Bind(FC.EVT_FC_LEFT_DOWN, self.OnLeft) + Rect2.Name = 'blue' + + self.Show() + Canvas.ZoomToBB() + + def OnLeft(self, object): + print "Rect %s got hit"%object.Name + + def OnMove(self, event): + """ + Updates the status bar with the world coordinates + + """ + self.SetStatusText("%.2f, %.2f"%tuple(event.Coords)) + +app = wx.App(False) +F = DrawFrame(None, title="FloatCanvas Demo App", size=(700,700) ) +app.MainLoop() + + + + + + + + + + + + + diff --git a/samples/floatcanvas/BNAEditor.py b/samples/floatcanvas/BNAEditor.py new file mode 100755 index 00000000..f0693a5b --- /dev/null +++ b/samples/floatcanvas/BNAEditor.py @@ -0,0 +1,353 @@ +#!/usr/bin/env python + +""" +BNA-Editor: a simple app for editing polygons in BNA files + +BNA is a simple text format for storing polygons in lat-long coordinates. + +""" +import os, sys +import sets + +import numpy as N + +#### import local version: +#sys.path.append("..") +#from floatcanvas import NavCanvas, FloatCanvas + +## import the installed version +from wx.lib.floatcanvas import NavCanvas, FloatCanvas + +import wx +import sys + +if len(sys.argv) > 1: + StartFileName = sys.argv[1] +else: + StartFileName = None + +### These utilities are required to load and save BNA data. +class BNAData: + """ + Class to store the full set of data in a BNA file + + """ + def __init__(self, Filename = None): + self.Filename = Filename + self.PointsData = None + self.Filename = None + self.Names = None + self.Types = None + if Filename is not None: + self.Load(Filename) + + def __getitem__(self,index): + return (self.PointsData[index], self.Names[index]) + + def __len__(self): + return len(self.PointsData) + + def Save(self, filename = None): + if not filename: + filename = self.filename + file = open(filename, 'w') + for i, points in enumerate(self.PointsData): + file.write('"%s","%s", %i\n'%(self.Names[i],self.Types[i],len(points) ) ) + for p in points: + file.write("%.12f,%.12f\n"%(tuple(p))) + + def Load(self, filename): + #print "Loading:", filename + file = open(filename,'rU') + + self.Filename = filename + self.PointsData = [] + self.Names = [] + self.Types = [] + while 1: + line = file.readline() + if not line: + break + line = line.strip() + Name, line = line.split('","') + Name = Name[1:] + Type,line = line.split('",') + num_points = int(line) + self.Types.append(Type) + self.Names.append(Name) + polygon = N.zeros((num_points,2),N.float) + for i in range(num_points): + polygon[i,:] = map(float, file.readline().split(',')) + self.PointsData.append(polygon) + + file.close() + return None + +class DrawFrame(wx.Frame): + """ + A frame used for the BNA Editor + + """ + + def __init__(self,parent, id,title,position,size): + wx.Frame.__init__(self,parent, id,title,position, size) + + ## Set up the MenuBar + MenuBar = wx.MenuBar() + + FileMenu = wx.Menu() + + OpenMenu = FileMenu.Append(wx.ID_ANY, "&Open","Open BNA") + self.Bind(wx.EVT_MENU, self.OpenBNA, OpenMenu) + + SaveMenu = FileMenu.Append(wx.ID_ANY, "&Save","Save BNA") + self.Bind(wx.EVT_MENU, self.SaveBNA, SaveMenu) + + CloseMenu = FileMenu.Append(wx.ID_ANY, "&Close","Close Application") + self.Bind(wx.EVT_MENU, self.OnQuit, CloseMenu) + + MenuBar.Append(FileMenu, "&File") + + view_menu = wx.Menu() + ZoomMenu = view_menu.Append(wx.ID_ANY, "Zoom to &Fit","Zoom to fit the window") + self.Bind(wx.EVT_MENU, self.ZoomToFit, ZoomMenu) + MenuBar.Append(view_menu, "&View") + + help_menu = wx.Menu() + AboutMenu = help_menu.Append(wx.ID_ANY, "&About", + "More information About this program") + self.Bind(wx.EVT_MENU, self.OnAbout, AboutMenu) + MenuBar.Append(help_menu, "&Help") + + self.SetMenuBar(MenuBar) + + self.CreateStatusBar() + # Add the Canvas + self.Canvas = NavCanvas.NavCanvas(self,-1,(500,500), + Debug = 0, + BackgroundColor = "DARK SLATE BLUE" + ).Canvas + + wx.EVT_CLOSE(self, self.OnCloseWindow) + + FloatCanvas.EVT_MOTION(self.Canvas, self.OnMove ) + FloatCanvas.EVT_LEFT_UP(self.Canvas, self.OnLeftUp ) + FloatCanvas.EVT_LEFT_DOWN(self.Canvas, self.OnLeftDown) + + try: + self.FileDialog = wx.FileDialog(self, "Pick a BNA file",".","","*", wx.OPEN) + except wx._core.PyAssertionError: + self.FileDialog = None + + self.ResetSelections() + return None + + def ResetSelections(self): + self.SelectedPoly = None + self.SelectedPolyOrig = None + self.SelectedPoints = None + self.PointSelected = False + self.SelectedPointNeighbors = None + + def OnLeftDown(self,event): + if self.SelectedPoly: + self.DeSelectPoly() + self.Canvas.Draw() + + def OnAbout(self, event): + dlg = wx.MessageDialog(self, "This is a small program to demonstrate\n" + "the use of the FloatCanvas\n", + "About Me", wx.OK | wx.ICON_INFORMATION) + dlg.ShowModal() + dlg.Destroy() + + def ZoomToFit(self,event): + self.Canvas.ZoomToBB() + + def OpenBNA(self, event): + if self.FileDialog is None: + self.FileDialog = wx.FileDialog(self, "Pick a BNA file",style= wx.OPEN) + dlg = self.FileDialog + dlg.SetMessage("Pick a BNA file") + if dlg.ShowModal() == wx.ID_OK: + filename = dlg.GetPath() + self.LoadBNA(filename) + + def SaveBNA(self, event): + for i in self.ChangedPolys: + self.BNAFile.PointsData[i] = self.AllPolys[i].Points + dlg = wx.FileDialog(self, + message="Pick a BNA file", + style=wx.SAVE) + if dlg.ShowModal() == wx.ID_OK: + filename = dlg.GetPath() + self.BNAFile.Save(filename) + + def Clear(self,event = None): + self.Canvas.ClearAll() + self.Canvas.Draw(True) + + def OnQuit(self,event): + self.Close(True) + + def OnCloseWindow(self, event): + self.Destroy() + + def OnMove(self, event): + """ + Updates the status bar with the world coordinates + And moves a point if there is one + + """ + self.SetStatusText("%.4f, %.4f"%tuple(event.Coords)) + + if self.PointSelected: + PolyPoints = self.SelectedPoly.Points + Index = self.SelectedPoints.Index + dc = wx.ClientDC(self.Canvas) + PixelCoords = event.GetPosition() + dc.SetPen(wx.Pen('WHITE', 2, wx.SHORT_DASH)) + dc.SetLogicalFunction(wx.XOR) + if self.SelectedPointNeighbors is None: + self.SelectedPointNeighbors = N.zeros((3,2), N.float) + #fixme: This feels very inelegant! + if Index == 0: + self.SelectedPointNeighbors[0] = self.SelectedPoly.Points[-1] + self.SelectedPointNeighbors[1:3] = self.SelectedPoly.Points[:2] + elif Index == len(self.SelectedPoly.Points)-1: + self.SelectedPointNeighbors[0:2] = self.SelectedPoly.Points[-2:] + self.SelectedPointNeighbors[2] = self.SelectedPoly.Points[0] + else: + self.SelectedPointNeighbors = self.SelectedPoly.Points[Index-1:Index+2] + self.SelectedPointNeighbors = self.Canvas.WorldToPixel(self.SelectedPointNeighbors) + else: + dc.DrawLines(self.SelectedPointNeighbors) + self.SelectedPointNeighbors[1] = PixelCoords + dc.DrawLines(self.SelectedPointNeighbors) + + def OnLeftUp(self, event): + if self.PointSelected: + self.SelectedPoly.Points[self.SelectedPoints.Index] = event.GetCoords() + self.SelectedPoly.SetPoints(self.SelectedPoly.Points, copy = False) + self.SelectedPoints.SetPoints(self.SelectedPoly.Points, copy = False) + self.PointSelected = False + self.SelectedPointNeighbors = None + self.SelectedPoly.HasChanged = True + self.Canvas.Draw() + + def DeSelectPoly(self): + Canvas = self.Canvas + if self.SelectedPoly.HasChanged: + self.ChangedPolys.add(self.SelectedPolyOrig.BNAIndex) + self.SelectedPolyOrig.SetPoints(self.SelectedPoly.Points, copy = False) + self.Canvas.Draw(Force = True) + Canvas.RemoveObject(self.SelectedPoly) + Canvas.RemoveObject(self.SelectedPoints) + self.ResetSelections() + + def SelectPoly(self, Object): + Canvas = self.Canvas + if Object is self.SelectedPolyOrig: + pass + else: + if self.SelectedPoly is not None: + self.DeSelectPoly() + self.SelectedPolyOrig = Object + self.SelectedPoly = Canvas.AddPolygon(Object.Points, + LineWidth = 1, + LineColor = "Red", + FillColor = None, + InForeground = True) + self.SelectedPoly.HasChanged = False + # Draw points on the Vertices of the Selected Poly: + self.SelectedPoints = Canvas.AddPointSet(Object.Points, + Diameter = 4, + Color = "Red", + InForeground = True) + self.SelectedPoints.HitLineWidth = 8 # make it a bit easier to hit + self.SelectedPoints.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.SelectPointHit) + Canvas.Draw() + + def SelectPointHit(self, PointSet): + PointSet.Index = PointSet.FindClosestPoint(PointSet.HitCoords) + self.PointSelected = True + + def LoadBNA(self, filename): + self.ResetSelections() + self.Canvas.ClearAll() + self.Canvas.SetProjectionFun('FlatEarth') + try: + AllPolys = [] + self.BNAFile = BNAData(filename) + print "loaded BNAFile:", self.BNAFile.Filename + for i, shoreline in enumerate(self.BNAFile.PointsData): + Poly = self.Canvas.AddPolygon(shoreline, + LineWidth = 1, + LineColor = "Black", + FillColor = "Brown", + FillStyle = 'Solid') + Poly.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.SelectPoly) + Poly.BNAIndex = i + AllPolys.append(Poly) + self.Canvas.ZoomToBB() + self.ChangedPolys = sets.Set() + self.AllPolys = AllPolys + except: + #raise + dlg = wx.MessageDialog(None, + 'There was something wrong with the selected bna file', + 'File Loading Error', + wx.OK | wx.ICON_ERROR) + dlg.ShowModal() + dlg.Destroy() + + +class BNAEditor(wx.App): + """ + 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. + """ + + def __init__(self, *args, **kwargs): + wx.App.__init__(self, *args, **kwargs) + + def OnInit(self): + frame = DrawFrame(None, -1, "BNA Editor",wx.DefaultPosition,(700,700)) + + self.SetTopWindow(frame) + frame.Show() + + if StartFileName: + frame.LoadBNA(StartFileName) + else: + ##frame.LoadBNA("Tests/Small.bna") + frame.LoadBNA("Tiny.bna") + return True + + +app = BNAEditor(False)# put in True if you want output to go to it's own window. +app.MainLoop() + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/floatcanvas/BarPlot.py b/samples/floatcanvas/BarPlot.py new file mode 100755 index 00000000..ff8e4566 --- /dev/null +++ b/samples/floatcanvas/BarPlot.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python + +import wx +## import the installed version +from wx.lib.floatcanvas import NavCanvas, FloatCanvas + +## import a local version +#import sys +#sys.path.append("../") +#from floatcanvas import NavCanvas, FloatCanvas + +import numpy as N +from numpy import random as random + +NumChannels = 200 +MaxValue = 2000 +#MaxValue = 2**24 + + +def YScaleFun(center): + """ + Function that returns a scaling vector to scale y data to same range as x data + + This is used by FloatCanvas as a "projection function", so that you can have + a different scale for X and Y. With the default projection, X and Y are the same scale. + + """ + + # center gets ignored in this case + return N.array((1, float(NumChannels)/MaxValue), N.float) + +def ScaleWorldToPixel(self, Lengths): + """ + This is a new version of a function that will get passed to the + drawing functions of the objects, to Change a length from world to + pixel coordinates. + + This version uses the "ceil" function, so that fractional pixel get + rounded up, rather than down. + + Lengths should be a NX2 array of (x,y) coordinates, or + a 2-tuple, or sequence of 2-tuples. + """ + return N.ceil(( (N.asarray(Lengths, N.float)*self.TransformVector) )).astype('i') + + +class DrawFrame(wx.Frame): + + """ + A frame used for the FloatCanvas Demo + + """ + + def __init__(self, *args, **kwargs): + wx.Frame.__init__(self, *args, **kwargs) + + self.CreateStatusBar() + + # Add the Canvas + FloatCanvas.FloatCanvas.ScaleWorldToPixel = ScaleWorldToPixel + NC = NavCanvas.NavCanvas(self,-1, + size = (500,500), + BackgroundColor = "DARK SLATE BLUE", + ProjectionFun = YScaleFun, + ) + + self.Canvas = Canvas = NC.Canvas + #self.Canvas.ScaleWorldToPixel = ScaleWorldToPixel + + FloatCanvas.EVT_MOTION(self.Canvas, self.OnMove ) + + self.Values = random.randint(0, MaxValue, (NumChannels,)) + + self.Bars = [] + self.BarWidth = 0.75 + # add an X axis + Canvas.AddLine(((0,0), (NumChannels, 0 )),) + for x in N.linspace(1, NumChannels, 11): + Canvas.AddText("%i"%x, (x-1+self.BarWidth/2,0), Position="tc") + + for i, Value in enumerate(self.Values): + bar = Canvas.AddRectangle(XY=(i, 0), + WH=(self.BarWidth, Value), + LineColor = None, + LineStyle = "Solid", + LineWidth = 1, + FillColor = "Red", + FillStyle = "Solid", + ) + self.Bars.append(bar) + + # Add a couple a button the Toolbar + + tb = NC.ToolBar + tb.AddSeparator() + + ResetButton = wx.Button(tb, label="Reset") + tb.AddControl(ResetButton) + ResetButton.Bind(wx.EVT_BUTTON, self.ResetData) + +# PlayButton = wx.Button(tb, wx.ID_ANY, "Run") +# tb.AddControl(PlayButton) +# PlayButton.Bind(wx.EVT_BUTTON, self.RunTest) + tb.Realize() + + self.Show() + Canvas.ZoomToBB() + Canvas.Draw(True) + + + def OnMove(self, event): + """ + Updates the status bar with the world coordinates + + """ + channel, value = event.Coords + if 0 < channel < NumChannels : + channel = "%i,"%(channel+1) + else: + channel = "" + + if value >=0: + value = "%3g"%value + else: + value = "" + self.SetStatusText("Channel: %s Value: %s"%(channel, value)) + + def ResetData(self, event): + self.Values = random.randint(0, MaxValue, (NumChannels,)) + for i, bar in enumerate(self.Bars): + bar.SetShape(bar.XY, (self.BarWidth, self.Values[i])) + self.Canvas.Draw(Force=True) + +app = wx.App(False) +F = DrawFrame(None, title="FloatCanvas Demo App", size=(700,700) ) +app.MainLoop() + + + + + + + + + + + + + diff --git a/samples/floatcanvas/BouncingBall.py b/samples/floatcanvas/BouncingBall.py new file mode 100755 index 00000000..3efc35b8 --- /dev/null +++ b/samples/floatcanvas/BouncingBall.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python + +""" +A test of some simple animation + +this is very old-style code: don't imitate it! + +""" + +import wx +import numpy as np + +## import local version: +import sys + +#ver = 'local' +ver = 'installed' + +if ver == 'installed': ## import the installed version + from wx.lib.floatcanvas import NavCanvas + from wx.lib.floatcanvas import FloatCanvas + print "using installed version:", wx.lib.floatcanvas.__version__ +elif ver == 'local': + ## import a local version + import sys + sys.path.append("..") + from floatcanvas import NavCanvas + from floatcanvas import FloatCanvas + +FC = FloatCanvas + +class MovingObjectMixin: # Borrowed from MovingElements.py + """ + Methods required for a Moving object + + """ + + def GetOutlinePoints(self): + BB = self.BoundingBox + OutlinePoints = np.array( ( (BB[0,0], BB[0,1]), + (BB[0,0], BB[1,1]), + (BB[1,0], BB[1,1]), + (BB[1,0], BB[0,1]), + ) + ) + + return OutlinePoints + + +class Ball(MovingObjectMixin, FloatCanvas.Circle): + def __init__(self, XY, Velocity, Radius=2.0, **kwargs): + self.Velocity = np.asarray(Velocity, np.float).reshape((2,)) + self.Radius = Radius + self.Moving = False + FloatCanvas.Circle.__init__(self, XY, Diameter=Radius*2, FillColor="red", **kwargs) + +class DrawFrame(wx.Frame): + def __init__(self, *args, **kwargs): + wx.Frame.__init__(self, *args, **kwargs) + + ## Set up the MenuBar + + MenuBar = wx.MenuBar() + + file_menu = wx.Menu() + item = file_menu.Append(wx.ID_ANY, "E&xit","Terminate the program") + self.Bind(wx.EVT_MENU, self.OnQuit, item) + MenuBar.Append(file_menu, "&File") + + + self.SetMenuBar(MenuBar) + + self.CreateStatusBar() + self.SetStatusText("") + + wx.EVT_CLOSE(self, self.OnCloseWindow) + + # Add the buttons + ResetButton = wx.Button(self, label="Reset") + ResetButton.Bind(wx.EVT_BUTTON, self.OnReset) + + StartButton = wx.Button(self, label="Start") + StartButton.Bind(wx.EVT_BUTTON, self.OnStart) + + StopButton = wx.Button(self, label="Stop") + StopButton.Bind(wx.EVT_BUTTON, self.OnStop) + + butSizer = wx.BoxSizer(wx.HORIZONTAL) + butSizer.Add(StartButton, 0, wx.RIGHT, 5 ) + butSizer.Add(ResetButton, 0, wx.RIGHT, 5) + butSizer.Add(StopButton, 0, ) + # Add the Canvas + NC = NavCanvas.NavCanvas(self, -1, (500,500), + Debug = False, + BackgroundColor = "BLUE") + + self.Canvas = NC.Canvas + self.Initialize(None) + + # lay it out: + S = wx.BoxSizer(wx.VERTICAL) + S.Add(butSizer, 0, wx.ALIGN_CENTER | wx.ALL, 5) + S.Add(NC, 1, wx.EXPAND) + self.SetSizer(S) + + self.timer = wx.Timer(self) + self.Bind(wx.EVT_TIMER, self.MoveBall, self.timer) + + self.Show(True) + + def OnQuit(self,event): + self.Close(True) + + def OnCloseWindow(self, event): + self.Destroy() + + def Initialize(self, event=None): + Canvas = self.Canvas + + #Add the floor + Canvas.AddLine(( (0, 0), (100, 0) ), LineWidth=4, LineColor="Black") + # add the wall: + Canvas.AddRectangle( (0,0), (10,50), FillColor='green') + + # add the ball: + self.Ball = Ball( (5, 52), (2, 0), InForeground=True ) + Canvas.AddObject( self.Ball ) + # to capture the mouse to move the ball + self.Ball.Bind(FC.EVT_FC_LEFT_DOWN, self.BallHit) + Canvas.Bind(FC.EVT_MOTION, self.OnMove ) + Canvas.Bind(FC.EVT_LEFT_UP, self.OnLeftUp ) + + wx.CallAfter(Canvas.ZoomToBB) + + def BallHit(self, object): + print "the ball was clicked" + self.Ball.Moving = True + + def OnMove(self, event): + """ + Updates the status bar with the world coordinates + and moves the object it is clicked on + + """ + self.SetStatusText("%.4f, %.4f"%tuple(event.Coords)) + if self.Ball.Moving: + self.Ball.SetPoint(event.Coords) + self.Canvas.Draw(True) + + def OnLeftUp(self, event): + self.Ball.Moving = False + + def OnReset(self, event=None): + self.Ball.SetPoint( (5, 52) ) + self.Ball.Velocity = np.array((1.5, 0.0)) + self.Canvas.Draw(True) + + def OnStart(self, event=None): + self.timer.Start(20) + + def OnStop(self, event=None): + self.timer.Stop() + + def MoveBall(self, event=None): + ball = self.Ball + + dt = .1 + g = 9.806 + m = 1 + A = np.pi*(ball.Radius/100)**2 # radius in cm + Cd = 0.47 + rho = 1.3 + + if not ball.Moving: # don't do this if the user is moving it + vel = ball.Velocity + pos = ball.XY + + # apply drag + vel -= np.sign(vel) * ((0.5 * Cd * rho * A * vel**2) / m * dt) + # apply gravity + vel[1] -= g * dt + # move the ball + pos += dt * vel + # check if it's on the wall + if pos[1] <= 52. and pos[0] <= 10.: + #reverse velocity + vel[1] *= -1.0 + pos[1] = 52. + # check if it's hit the floor + elif pos[1] <= ball.Radius: + #reverse velocity + vel[1] *= -1.0 + pos[1] = ball.Radius + + self.Ball.SetPoint( pos ) + self.Canvas.Draw(True) + wx.GetApp().Yield(onlyIfNeeded=True) + +class DemoApp(wx.App): + def OnInit(self): + frame = DrawFrame(None, -1, "Simple Drawing Window",wx.DefaultPosition, (700,700) ) + + self.SetTopWindow(frame) + + return True + +if __name__ == "__main__": + + app = DemoApp(0) + app.MainLoop() + + diff --git a/samples/floatcanvas/Chart.py b/samples/floatcanvas/Chart.py new file mode 100755 index 00000000..bf079031 --- /dev/null +++ b/samples/floatcanvas/Chart.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python + +import wx + +## import the installed version +from wx.lib.floatcanvas import NavCanvas, FloatCanvas + +## import a local version +#import sys +#sys.path.append("../") +#from floatcanvas import NavCanvas, FloatCanvas + + +class DrawFrame(wx.Frame): + + """ + A frame used for the FloatCanvas Demo + + """ + + def __init__(self, *args, **kwargs): + wx.Frame.__init__(self, *args, **kwargs) + + self.CreateStatusBar() + + # Add the Canvas + Canvas = NavCanvas.NavCanvas(self,-1, + size = (500,500), + ProjectionFun = None, + Debug = 0, + BackgroundColor = "White", + ).Canvas +# Canvas = FloatCanvas.FloatCanvas(self,-1, +# size = (500,500), +# ProjectionFun = None, +# Debug = 0, +# BackgroundColor = "White", +# ) + + self.Canvas = Canvas + + FloatCanvas.EVT_MOTION(self.Canvas, self.OnMove ) + FloatCanvas.EVT_LEFT_DOWN(self.Canvas, self.OnLeft) + + + # Some default sizes: + self.LineHeight = 1 + self.TextWidth = 8 + self.SpaceWidth = 1 + self.Labels = ["SW Tasks", "Set RX Rf"] + ["A Row Label"]*16 + self.NumRows = len(self.Labels) + + self.BuildChartBackground() + self.AddLabels() + self.Show() + Canvas.MinScale=28 + Canvas.MaxScale=28 + Canvas.ZoomToBB() + + def BuildChartBackground(self): + Canvas = self.Canvas + top = 0 + bottom = -(self.LineHeight * self.NumRows) + width = self.SpaceWidth * 16 + self.TextWidth + # put in the rows: + for i in range(1, self.NumRows+1, 2): + Canvas.AddRectangle((0-self.TextWidth, -i*self.LineHeight), + (width, self.LineHeight), + LineColor = None, + FillColor = "LightGrey", + FillStyle = "Solid",) + + # put a dashed line in every 1 unit: + for i in range(16): + Canvas.AddLine(((i*self.SpaceWidth,bottom),(i*self.SpaceWidth,top)), + LineColor = "Black", + LineStyle = "Dot", + # or "Dot", "ShortDash", "LongDash","ShortDash", "DotDash" + LineWidth = 1,) + def AddLabels(self): + Canvas = self.Canvas + + for i, label in enumerate(self.Labels): + Canvas.AddScaledText(label, + ( -self.TextWidth, -(i+0.2)*self.LineHeight ), + Size = 0.6 * self.LineHeight, + Color = "Black", + BackgroundColor = None, + Family = wx.MODERN, + Style = wx.NORMAL, + Weight = wx.NORMAL, + Underlined = False, + Position = 'tl', + ) + + def OnMove(self, event): + """ + Updates the status bar with the world coordinates + + """ + self.SetStatusText("%.2f, %.2f"%tuple(event.Coords)) + + def OnLeft(self, event): + """ + Prints various info about the state of the canvas to stdout + + """ + print "Scale is:", self.Canvas.Scale + + +app = wx.App(False) +F = DrawFrame(None, title="FloatCanvas Demo App", size=(700,700) ) +app.MainLoop() + + + + + + + + + + + + + diff --git a/samples/floatcanvas/ClickableBoxes.py b/samples/floatcanvas/ClickableBoxes.py new file mode 100755 index 00000000..e16566e3 --- /dev/null +++ b/samples/floatcanvas/ClickableBoxes.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python + +""" +This is a little demo of how to make clickable (and changeable) objects with +FloatCanvas + +Also an example of constant size, rather than the usual zooming and panning + +Developed as an answer to a question on the wxPYhton mailing lilst: + "get panel id while dragging across several panels' + April 5, 2012 + +""" + +import random +import wx + +## import the installed version +from wx.lib.floatcanvas import NavCanvas, FloatCanvas + +## import a local version +#import sys +#sys.path.append("../") +#from floatcanvas import FloatCanvas as FC + + +colors = [ (255, 0 , 0 ), + (0 , 255, 0 ), + (0 , 0, 255), + (255, 255, 0 ), + (255, 0, 255), + (0 , 255, 255), + ] + + +class DrawFrame(wx.Frame): + + """ + A frame used for the Demo + + """ + + def __init__(self, *args, **kwargs): + wx.Frame.__init__(self, *args, **kwargs) + # Add the Canvas + Canvas = FC.FloatCanvas(self, + size = (500,500), + ProjectionFun = None, + Debug = 0, + BackgroundColor = "Black", + ) + + self.Canvas = Canvas + + self.Canvas.Bind(wx.EVT_SIZE, self.OnSize) + + # build the squares: + w = 10 + dx = 14 + for i in range(9): + for j in range(9): + Rect = Canvas.AddRectangle((i*dx, j*dx), (w, w), FillColor="White", LineStyle = None) + Outline = Canvas.AddRectangle((i*dx, j*dx), (w, w), + FillColor=None, + LineWidth=4, + LineColor='Red', + LineStyle=None) + Rect.indexes = (i,j) + Rect.outline = Outline + Rect.Bind(FC.EVT_FC_LEFT_DOWN, self.SquareHitLeft) + Rect.Bind(FC.EVT_FC_ENTER_OBJECT, self.SquareEnter) + Rect.Bind(FC.EVT_FC_LEAVE_OBJECT, self.SquareLeave) + + self.Show() + Canvas.ZoomToBB() + + def SquareHitLeft(self, square): + print "square hit:", square.indexes + # set a random color + c = random.sample(colors, 1)[0] + square.SetFillColor( c ) + self.Canvas.Draw(True) + + def SquareEnter(self, square): + print "entering square:", square.indexes + square.outline.SetLineStyle("Solid") + self.Canvas.Draw(True) + + def SquareLeave(self, square): + print "leaving square:", square.indexes + square.outline.SetLineStyle(None) + self.Canvas.Draw(True) + + + def OnSize(self, event): + """ + re-zooms the canvas to fit the window + + """ + print "in OnSize" + self.Canvas.ZoomToBB() + event.Skip() + +app = wx.App(False) +F = DrawFrame(None, title="FloatCanvas Demo App", size=(700,700) ) +app.MainLoop() + + + + + + + + + + + + + diff --git a/samples/floatcanvas/DrawBot.py b/samples/floatcanvas/DrawBot.py new file mode 100755 index 00000000..9e13881e --- /dev/null +++ b/samples/floatcanvas/DrawBot.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python + +""" +DrawBot.py + +This a a demo of how one can use the FloatCanvas to do a drawing similar to one of the "DrawBot" demos: + + +http://just.letterror.com/ltrwiki/DrawBot + +I think it's easier with FloatCavnas, and you get zoomign and scrolling to boot! + + +""" + +import wx +from math import * + +try: # see if there is a local FloatCanvas to use + import sys + sys.path.append("../") + from floatcanvas import NavCanvas, FloatCanvas + print "Using local FloatCanvas" +except ImportError: # Use the wxPython lib one + from wx.lib.floatcanvas import NavCanvas, FloatCanvas + print "Using installed FloatCanvas" + + +class DrawFrame(wx.Frame): + + """ + A frame used for the FloatCanvas Demo + + """ + + def __init__(self,parent, id,title,position,size): + wx.Frame.__init__(self,parent, id,title,position, size) + + # Add the Canvas + self.Canvas = NavCanvas.NavCanvas(self,-1,(500,500), + ProjectionFun = None, + Debug = 0, + BackgroundColor = "White", + ).Canvas + + + self.Show(True) + self.MakePic() + + return None + + def MakePic(self): + Canvas = self.Canvas + phi = (sqrt(5) + 1)/2 - 1 + oradius = 10.0 + for i in xrange(720): + radius = 1.5 * oradius * sin(i * pi/720) + Color = (255*(i / 720.), 255*( i / 720.), 255 * 0.25) + x = oradius + 0.25*i*cos(phi*i*2*pi) + y = oradius + 0.25*i*sin(phi*i*2*pi) + Canvas.AddCircle((x,y), + radius, + LineColor = "Black", + LineWidth = 2, + FillColor = Color, + ) + self.Canvas.ZoomToBB() + +app = wx.PySimpleApp() +DrawFrame(None, -1, "FloatCanvas Demo App", wx.DefaultPosition, (700,700) ) +app.MainLoop() + diff --git a/samples/floatcanvas/DrawRect.py b/samples/floatcanvas/DrawRect.py new file mode 100755 index 00000000..3fbcdb26 --- /dev/null +++ b/samples/floatcanvas/DrawRect.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python + +""" +A simple demo that shows how to use FloatCanvas to draw rectangles on the screen + +Note: this is now broken -- the events are not getting to the Rubber Band Box object. + It should be re-factored to use GUIMode +""" + + +import wx + +## import a local version +#import sys +#sys.path.append("..") +#from floatcanvas import NavCanvas, FloatCanvas, Resources, Utilities, GUIMode +#from floatcanvas.Utilities import GUI + +## import the installed version +from wx.lib.floatcanvas import NavCanvas, FloatCanvas +from wx.lib.floatcanvas.Utilities import GUI + +import numpy as N + +class DrawFrame(wx.Frame): + + """ + A frame used for the FloatCanvas Demo + + """ + + def __init__(self,parent, id,title,position,size): + wx.Frame.__init__(self,parent, id,title,position, size) + + self.CreateStatusBar() + # Add the Canvas + NC = NavCanvas.NavCanvas(self, + size= (500,500), + ProjectionFun = None, + Debug = 0, + BackgroundColor = "DARK SLATE BLUE", + ) + + self.Canvas = NC.Canvas + + FloatCanvas.EVT_MOTION(self.Canvas, self.OnMove ) + + # Add some buttons to the Toolbar + tb = NC.ToolBar + tb.AddSeparator() + + ClearButton = wx.Button(tb, wx.ID_ANY, "Clear") + tb.AddControl(ClearButton) + ClearButton.Bind(wx.EVT_BUTTON, self.Clear) + + DrawButton = wx.Button(tb, wx.ID_ANY, "StopDrawing") + tb.AddControl(DrawButton) + DrawButton.Bind(wx.EVT_BUTTON, self.SetDraw) + self.DrawButton = DrawButton + + tb.Realize() + + # Initialize a few values + self.Rects = [] + + self.RBBoxMode = GUI.RubberBandBox(self.NewRect) + self.Canvas.SetMode(self.RBBoxMode) + + self.Canvas.ZoomToBB() + + self.Show(True) + return None + + def Clear(self, event=None): + self.Rects = [] + self.Canvas.ClearAll() + self.Canvas.Draw() + + def SetDraw(self, event=None): + label = self.DrawButton.GetLabel() + if label == "Draw": + self.DrawButton.SetLabel("StopDrawing") + self.Canvas.SetMode(self.RBBoxMode) + elif label == "StopDrawing": + self.DrawButton.SetLabel("Draw") + self.Canvas.SetMode(GUIMode.GUIMouse()) + else: # huh? + pass + + def NewRect(self, rect): + self.Rects.append(self.Canvas.AddRectangle(*rect, LineWidth=4)) + self.Canvas.Draw(True) + + def OnMove(self, event): + """ + Updates the status bar with the world coordinates + + """ + self.SetStatusText("%.4f, %.4f"%tuple(event.Coords)) + event.Skip() + +app = wx.PySimpleApp() +DrawFrame(None, -1, "FloatCanvas Rectangle Drawer", wx.DefaultPosition, (700,700) ) +app.MainLoop() + + + + + + + + + + + + + diff --git a/samples/floatcanvas/GridDemo.py b/samples/floatcanvas/GridDemo.py new file mode 100755 index 00000000..4143e3bf --- /dev/null +++ b/samples/floatcanvas/GridDemo.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python + +""" +A simple demo to show how to do grids + +""" + +import wx + + +try: + # See if there is a local copy + import sys + sys.path.append("../") + from floatcanvas import NavCanvas, FloatCanvas +except ImportError: + from wx.lib.floatcanvas import NavCanvas, FloatCanvas + +class DrawFrame(wx.Frame): + + """ + A frame used for the FloatCanvas Demo + + """ + + def __init__(self, *args, **kwargs): + wx.Frame.__init__(self, *args, **kwargs) + + self.CreateStatusBar() + + # Add the Canvas + Canvas = NavCanvas.NavCanvas(self,-1, + size = (500,500), + ProjectionFun = None, + Debug = 0, + BackgroundColor = "DARK SLATE BLUE", + ).Canvas + + + Point = (45,40) + Box = Canvas.AddCircle(Point, + Diameter = 10, + FillColor = "Black", + LineColor = "Red", + LineWidth = 6) + + # Crosses: + Grid = FloatCanvas.DotGrid( Spacing=(1, .5), Size=2, Color="Cyan", Cross=True, CrossThickness=2) + #Dots: + #Grid = FloatCanvas.DotGrid( (0.5, 1), Size=3, Color="Red") + + Canvas.GridUnder = Grid + #Canvas.GridOver = Grid + + FloatCanvas.EVT_MOTION(Canvas, self.OnMove ) + + self.Show() + Canvas.ZoomToBB() + + def OnMove(self, event): + """ + Updates the status bar with the world coordinates + + """ + self.SetStatusText("%.2f, %.2f"%tuple(event.Coords)) + + + +app = wx.App(False) # true to get its own output window. +F = DrawFrame(None, title="FloatCanvas Demo App", size=(700,700) ) +app.MainLoop() + + + + + + + + + + + + + diff --git a/samples/floatcanvas/GroupDeleteDemo.py b/samples/floatcanvas/GroupDeleteDemo.py new file mode 100644 index 00000000..a09034f2 --- /dev/null +++ b/samples/floatcanvas/GroupDeleteDemo.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python + +""" +A small demo of how to use Groups of Objects + +""" + +import wx + +## import the installed version +from wx.lib.floatcanvas import NavCanvas, FloatCanvas + +## import a local version +#import sys +#sys.path.append("..") +#from floatcanvas import NavCanvas, FloatCanvas + +class DrawFrame(wx.Frame): + + """ + A frame used for the FloatCanvas Demo + + """ + + def __init__(self, *args, **kwargs): + wx.Frame.__init__(self, *args, **kwargs) + + self.CreateStatusBar() + + # Add the Canvas + NC = NavCanvas.NavCanvas(self,-1, + size = (500,500), + ProjectionFun = None, + Debug = 0, + BackgroundColor = "DARK SLATE BLUE", + ) + Canvas = NC.Canvas + self.Canvas = Canvas + + FloatCanvas.EVT_MOTION(self.Canvas, self.OnMove ) + + Point = (45,40) + + ## create a few Objects: + C = FloatCanvas.Circle((0, 0), 10, FillColor="Red") + R = FloatCanvas.Rectangle((5, 5),(15, 8), FillColor="Blue") + E = FloatCanvas.Ellipse((1.5, 1.5), (12, 8), FillColor="Purple") + C2 = FloatCanvas.Circle((0, 5), 10, FillColor="cyan") + T = FloatCanvas.Text("Group A", (5.5, 5.5), Position="cc", Size = 16, Weight=wx.BOLD, Family=wx.SWISS) + + self.GroupA = FloatCanvas.Group((R,C,E)) + self.GroupA.AddObjects((C2,T)) + Canvas.AddObject(self.GroupA) + + + ## create another Groups of objects + + R = FloatCanvas.Rectangle((15, 15),(10, 18), FillColor="orange") + E = FloatCanvas.Ellipse((22, 28), (12, 8), FillColor="yellow") + C = FloatCanvas.Circle((25, 20), 15, FillColor="Green") + C2 = FloatCanvas.Circle((12, 22), 10, FillColor="cyan") + T = FloatCanvas.Text("Group B", (19, 24), Position="cc", Size = 16, Weight=wx.BOLD, Family=wx.SWISS) + + self.GroupB = FloatCanvas.Group((R,E,C,C2,T)) + Canvas.AddObject(self.GroupB) + + self.Groups = {"A":self.GroupA, "B":self.GroupB} + + # Add a couple of tools to the Canvas Toolbar + + tb = NC.ToolBar +# tb.AddSeparator() + + for Group in self.Groups.keys(): + Button = wx.Button(tb, wx.ID_ANY, "Remove %s"%Group) + tb.AddControl(Button) + Button.Bind(wx.EVT_BUTTON, lambda evt, group=Group: self.RemoveGroup(evt, group)) + Button = wx.Button(tb, wx.ID_ANY, "Replace%s"%Group) + tb.AddControl(Button) + Button.Bind(wx.EVT_BUTTON, lambda evt, group=Group: self.ReplaceGroup(evt, group)) + tb.Realize() + + self.Show() + Canvas.ZoomToBB() + + def OnMove(self, event): + """ + Updates the status bar with the world coordinates of the mouse position + + """ + self.SetStatusText("%.2f, %.2f"%tuple(event.Coords)) + + def RemoveGroup(self, evt, group=""): + print "removing group:", group + G = self.Groups[group] + self.Canvas.RemoveObject(G) + self.Canvas.Draw(Force=True) + + def ReplaceGroup(self, evt, group=""): + print "replacing group:", group + G = self.Groups[group] + self.Canvas.AddObject(G) + self.Canvas.Draw(Force=True) + + +app = wx.App(False) +F = DrawFrame(None, title="FloatCanvas Demo App", size=(700,700) ) +app.MainLoop() + + + + + + + + + + + + + diff --git a/samples/floatcanvas/GroupDemo.py b/samples/floatcanvas/GroupDemo.py new file mode 100644 index 00000000..a235dc78 --- /dev/null +++ b/samples/floatcanvas/GroupDemo.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python + +""" +A small demo of how to use Groups of Objects + +""" + +import wx + +## import the installed version +from wx.lib.floatcanvas import NavCanvas, FloatCanvas + +## import a local version +#import sys +#sys.path.append("..") +#from floatcanvas import NavCanvas, FloatCanvas + +class DrawFrame(wx.Frame): + + """ + A frame used for the FloatCanvas Demo + + """ + + def __init__(self, *args, **kwargs): + wx.Frame.__init__(self, *args, **kwargs) + + self.CreateStatusBar() + + # Add the Canvas + NC = NavCanvas.NavCanvas(self,-1, + size = (500,500), + ProjectionFun = None, + Debug = 0, + BackgroundColor = "DARK SLATE BLUE", + ) + Canvas = NC.Canvas + self.Canvas = Canvas + + FloatCanvas.EVT_MOTION(self.Canvas, self.OnMove ) + + Point = (45,40) + + ## create a few Objects: + C = FloatCanvas.Circle((0, 0), 10, FillColor="Red") + R = FloatCanvas.Rectangle((5, 5),(15, 8), FillColor="Blue") + E = FloatCanvas.Ellipse((1.5, 1.5), (12, 8), FillColor="Purple") + C2 = FloatCanvas.Circle((0, 5), 10, FillColor="cyan") + T = FloatCanvas.Text("Group A", (5.5, 5.5), Position="cc", Size = 16, Weight=wx.BOLD, Family=wx.SWISS) + + self.GroupA = FloatCanvas.Group((R,C,E)) + self.GroupA.AddObjects((C2,T)) + Canvas.AddObject(self.GroupA) + + + ## create another Groups of objects + + R = FloatCanvas.Rectangle((15, 15),(10, 18), FillColor="orange") + E = FloatCanvas.Ellipse((22, 28), (12, 8), FillColor="yellow") + C = FloatCanvas.Circle((25, 20), 15, FillColor="Green") + C2 = FloatCanvas.Circle((12, 22), 10, FillColor="cyan") + T = FloatCanvas.Text("Group B", (19, 24), Position="cc", Size = 16, Weight=wx.BOLD, Family=wx.SWISS) + + self.GroupB = FloatCanvas.Group((R,E,C,C2,T)) + Canvas.AddObject(self.GroupB) + + self.Groups = {"A":self.GroupA, "B":self.GroupB} + + # Add a couple of tools to the Canvas Toolbar + + tb = NC.ToolBar +# tb.AddSeparator() + + for Group in self.Groups.keys(): + Button = wx.Button(tb, wx.ID_ANY, "Hide/Show%s"%Group) + tb.AddControl(Button) + print Group + Button.Bind(wx.EVT_BUTTON, lambda evt, group=Group: self.HideGroup(evt, group)) + tb.Realize() + + self.Show() + Canvas.ZoomToBB() + + def OnMove(self, event): + """ + Updates the status bar with the world coordinates of the mouse position + + """ + self.SetStatusText("%.2f, %.2f"%tuple(event.Coords)) + + def HideGroup(self, evt, group=""): + G = self.Groups[group] + G.Visible = not G.Visible + self.Canvas.Draw(Force=True) + +app = wx.App(False) +F = DrawFrame(None, title="FloatCanvas Demo App", size=(700,700) ) +app.MainLoop() + + + + + + + + + + + + + diff --git a/samples/floatcanvas/Hexagons.py b/samples/floatcanvas/Hexagons.py new file mode 100755 index 00000000..8c0e622c --- /dev/null +++ b/samples/floatcanvas/Hexagons.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python + +""" +A simple demo to display a lot of hexagons + +This was an example someone had on the wxPython-users list + +""" +import wx +import wx.lib.colourdb + +## import local version: +#import sys +#sys.path.append("..") +#from floatcanvas import NavCanvas, FloatCanvas + +## import installed version +from wx.lib.floatcanvas import NavCanvas, FloatCanvas + +NumHexagons = 1000 + +import numpy as N +from numpy.random import uniform + +import random +import time + +class DrawFrame(wx.Frame): + """ + A frame used for the FloatCanvas Demo + + """ + def __init__(self, *args, **kwargs): + wx.Frame.__init__(self, *args, **kwargs) + + # Add the Canvas + self.Canvas = NavCanvas.NavCanvas(self,-1,(500,500), + ProjectionFun = None, + Debug = 1, + BackgroundColor = "DARK SLATE BLUE", + ).Canvas + self.MakeHexagons() + + self.Show(True) + print "Drawing the Hexagons" + self.Canvas.ZoomToBB() + + return None + + def MakeHexagons(self): + print "Building %i Hexagons"%NumHexagons + # get a list of colors for random colors + + wx.lib.colourdb.updateColourDB() + self.colors = wx.lib.colourdb.getColourList() + print "Max colors:", len(self.colors) + Canvas = self.Canvas + D = 1.0 + h = D *N.sqrt(3)/2 + Hex = N.array(((D , 0), + (D/2 , -h), + (-D/2, -h), + (-D , 0), + (-D/2, h), + (D/2 , h), + )) + Centers = uniform(-100, 100, (NumHexagons, 2)) + for center in Centers: + # scale the hexagon + Points = Hex * uniform(5,20) + #print Points + # shift the hexagon + Points = Points + center + #print Points + cf = random.randint(0,len(self.colors)-1) + #cf = 55 + H = Canvas.AddPolygon(Points, LineColor = None, FillColor = self.colors[cf]) + #print "BrushList is: %i long"%len(H.BrushList) + H.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.HexHit) + print "BrushList is: %i long"%len(H.BrushList) + + def HexHit(self, Hex): + print "A %s Hex was hit, obj ID: %i"%(Hex.FillColor, id(Hex)) + + + +app = wx.App(False) +DrawFrame(None, -1, "FloatCanvas Demo App", wx.DefaultPosition, (700,700) ) +app.MainLoop() + + + + + + + + + + + + + diff --git a/samples/floatcanvas/Map.py b/samples/floatcanvas/Map.py new file mode 100755 index 00000000..aa7b91b2 --- /dev/null +++ b/samples/floatcanvas/Map.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python + + +TestFileName = "../data/TestMap.png" + + +import wx + +from wx.lib.floatcanvas import NavCanvas, FloatCanvas + +#import sys +#sys.path.append("..") +#from floatcanvas import NavCanvas, FloatCanvas + + +class DrawFrame(wx.Frame): + + """ + A frame used for the FloatCanvas Demo + + """ + + def __init__(self, *args, **kwargs): + wx.Frame.__init__(self, *args, **kwargs) + + self.CreateStatusBar() + + # Add the Canvas + NC = NavCanvas.NavCanvas(self,-1, + size = (500,500), + ProjectionFun = None, + Debug = 0, + BackgroundColor = "White", + ) + self.Canvas = NC.Canvas + + self.LoadMap(TestFileName) + + FloatCanvas.EVT_MOTION(self.Canvas, self.OnMove ) + + self.Show() + self.Canvas.ZoomToBB() + + def LoadMap(self, filename): + Image = wx.Image(filename) + self.Canvas.AddScaledBitmap(Image, (0,0), Height = Image.GetSize()[1], Position = "tl") + + self.Canvas.AddPoint((0,0), Diameter=3) + self.Canvas.AddText("(0,0)", (0,0), Position="cl") + p = (Image.GetSize()[0],-Image.GetSize()[1]) + self.Canvas.AddPoint(p, Diameter=3) + self.Canvas.AddText("(%i,%i)"%p, p, Position="cl") + + self.Canvas.MinScale = 0.15 + self.Canvas.MaxScale = 1.0 + + def Binding(self, event): + print "Writing a png file:" + self.Canvas.SaveAsImage("junk.png") + print "Writing a jpeg file:" + self.Canvas.SaveAsImage("junk.jpg",wx.BITMAP_TYPE_JPEG) + + def OnMove(self, event): + """ + Updates the status bar with the world coordinates + + And moves a point if there is one selected + + """ + self.SetStatusText("%.2f, %.2f"%tuple(event.Coords)) + + +app = wx.App(False) +F = DrawFrame(None, title="FloatCanvas Demo App", size=(700,700) ) +app.MainLoop() + + diff --git a/samples/floatcanvas/MicroDemo.py b/samples/floatcanvas/MicroDemo.py new file mode 100755 index 00000000..7f0bc367 --- /dev/null +++ b/samples/floatcanvas/MicroDemo.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python + +import wx + +## import the installed version +from wx.lib.floatcanvas import NavCanvas, FloatCanvas + +## import a local version +#import sys +#sys.path.append("../") +#from floatcanvas import NavCanvas, FloatCanvas + + +class DrawFrame(wx.Frame): + + """ + A frame used for the FloatCanvas Demo + + """ + + def __init__(self, *args, **kwargs): + wx.Frame.__init__(self, *args, **kwargs) + + self.CreateStatusBar() + + # Add the Canvas + Canvas = NavCanvas.NavCanvas(self,-1, + size = (500,500), + ProjectionFun = None, + Debug = 0, + BackgroundColor = "DARK SLATE BLUE", + ).Canvas + + self.Canvas = Canvas + + FloatCanvas.EVT_MOTION(self.Canvas, self.OnMove ) + + Point = (45,40) + Text = Canvas.AddScaledText("A String", + Point, + 20, + Color = "Black", + BackgroundColor = None, + Family = wx.ROMAN, + Style = wx.NORMAL, + Weight = wx.NORMAL, + Underlined = False, + Position = 'bl', + InForeground = False) + Text.MinFontSize = 4 # the default is 1 + Text.DisappearWhenSmall = False #the default is True + + Rect = Canvas.AddRectangle((50, 20), (40,10), FillColor="Red", LineStyle = None) + Rect.MinSize = 4 # default is 1 + Rect.DisappearWhenSmall = False # defualt is True + + self.Show() + Canvas.ZoomToBB() + + + def OnMove(self, event): + """ + Updates the status bar with the world coordinates + + """ + self.SetStatusText("%.2f, %.2f"%tuple(event.Coords)) + +app = wx.App(False) +F = DrawFrame(None, title="FloatCanvas Demo App", size=(700,700) ) +app.MainLoop() + + + + + + + + + + + + + diff --git a/samples/floatcanvas/MiniDemo.py b/samples/floatcanvas/MiniDemo.py new file mode 100755 index 00000000..f8e4e79d --- /dev/null +++ b/samples/floatcanvas/MiniDemo.py @@ -0,0 +1,381 @@ +#!/usr/bin/env python + +import wx + +## import local version: +#import sys +#sys.path.append("../") +#from floatcanvas import NavCanvas, FloatCanvas + +## import installed version +from wx.lib.floatcanvas import NavCanvas, FloatCanvas + +import wx.lib.colourdb + +class DrawFrame(wx.Frame): + + """ + A frame used for the FloatCanvas Demo + + """ + + def __init__(self, *args, **kwargs): + wx.Frame.__init__(self, *args, **kwargs) + + ## Set up the MenuBar + + MenuBar = wx.MenuBar() + + file_menu = wx.Menu() + item = file_menu.Append(wx.ID_ANY, "&Close","Close this frame") + self.Bind(wx.EVT_MENU, self.OnQuit, item) + MenuBar.Append(file_menu, "&File") + + draw_menu = wx.Menu() + + item = draw_menu.Append(wx.ID_ANY, "&Draw Test","Run a test of drawing random components") + self.Bind(wx.EVT_MENU,self.DrawTest, item) + + item = draw_menu.Append(wx.ID_ANY, "&Move Test","Run a test of moving stuff in the background") + self.Bind(wx.EVT_MENU, self.MoveTest, item) + + item = draw_menu.Append(wx.ID_ANY, "&Clear","Clear the Canvas") + self.Bind(wx.EVT_MENU,self.Clear, item) + + MenuBar.Append(draw_menu, "&Draw") + + view_menu = wx.Menu() + item = view_menu.Append(wx.ID_ANY, "Zoom to &Fit","Zoom to fit the window") + self.Bind(wx.EVT_MENU,self.ZoomToFit, item) + MenuBar.Append(view_menu, "&View") + + help_menu = wx.Menu() + item = help_menu.Append(wx.ID_ANY, "&About", + "More information About this program") + self.Bind(wx.EVT_MENU, self.OnAbout, item) + MenuBar.Append(help_menu, "&Help") + + self.SetMenuBar(MenuBar) + + self.CreateStatusBar() + self.SetStatusText("") + + wx.EVT_CLOSE(self, self.OnCloseWindow) + + # Add the Canvas + self.Canvas = NavCanvas.NavCanvas(self, + ProjectionFun = None, + Debug = 0, + BackgroundColor = "DARK SLATE BLUE", + ).Canvas + + + self.Show(True) + + + # get a list of colors for random colors + wx.lib.colourdb.updateColourDB() + self.colors = wx.lib.colourdb.getColourList() + + self.LineStyles = FloatCanvas.DrawObject.LineStyleList.keys() + + return None + + def OnAbout(self, event): + dlg = wx.MessageDialog(self, "This is a small program to demonstrate\n" + "the use of the FloatCanvas\n", + "About Me", wx.OK | wx.ICON_INFORMATION) + dlg.ShowModal() + dlg.Destroy() + + def ZoomToFit(self,event): + self.Canvas.ZoomToBB() + + def Clear(self,event = None): + self.Canvas.ClearAll() + self.Canvas.Draw() + + def OnQuit(self,event): + self.Close(True) + + def OnCloseWindow(self, event): + self.Destroy() + + def DrawTest(self,event): + wx.GetApp().Yield() + import random + import numpy.random as RandomArray + Range = (-10,10) + + Canvas = self.Canvas + Canvas.ClearAll() + + # Set some zoom limits: + self.Canvas.MinScale = 15.0 + self.Canvas.MaxScale = 500.0 + + #### Draw a few Random Objects #### + + # Rectangles + for i in range(5): + xy = (random.uniform(Range[0],Range[1]),random.uniform(Range[0],Range[1])) + lw = random.randint(1,5) + cf = random.randint(0,len(self.colors)-1) + h = random.randint(1,5) + w = random.randint(1,5) + Canvas.AddRectangle(xy, (h,w), + LineWidth = lw, + FillColor = self.colors[cf]) + + # Ellipses + for i in range(5): + xy = (random.uniform(Range[0],Range[1]),random.uniform(Range[0],Range[1])) + lw = random.randint(1,5) + cf = random.randint(0,len(self.colors)-1) + h = random.randint(1,5) + w = random.randint(1,5) + Canvas.AddEllipse(xy, (h,w), + LineWidth = lw, + FillColor = self.colors[cf]) + + # Circles + for i in range(5): + point = (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(self.colors)-1) + cl = random.randint(0,len(self.colors)-1) + Canvas.AddCircle(point, D, + LineWidth = lw, + LineColor = self.colors[cl], + FillColor = self.colors[cf]) + Canvas.AddText("Circle # %i"%(i), + point, + Size = 12, + 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(self.colors)-1) + cl = random.randint(0,len(self.colors)-1) + Canvas.AddLine(points, LineWidth = lw, LineColor = self.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(self.colors)-1) + cl = random.randint(0,len(self.colors)-1) + Canvas.AddPolygon(points, + LineWidth = lw, + LineColor = self.colors[cl], + FillColor = self.colors[cf], + FillStyle = 'Solid') + + self.Canvas.ZoomToBB() + + def MoveTest(self,event=None): + print "Running: TestHitTestForeground" + wx.GetApp().Yield() + + self.UnBindAllMouseEvents() + Canvas = self.Canvas + + Canvas.ClearAll() + # Clear the zoom limits: + self.Canvas.MinScale = None + self.Canvas.MaxScale = None + + Canvas.SetProjectionFun(None) + + #Add a Hitable rectangle + w,h = 60, 20 + + dx = 80 + dy = 40 + x,y = 20, 20 + + color = "Red" + R = Canvas.AddRectangle((x,y), (w,h), + LineWidth = 2, + FillColor = color + ) + + R.Name = color + "Rectangle" + Canvas.AddText(R.Name, (x, y+h), Position = "tl") + + ## A set of Rectangles that move together + + ## NOTE: In a real app, it might be better to create a new + ## custom FloatCanvas DrawObject + + self.MovingRects = [] + x += dx + color = "LightBlue" + R = Canvas.AddRectangle((x,y), (w/2, h/2), + LineWidth = 2, + FillColor = color) + + R.HitFill = True + R.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.RectMoveLeft) + L = Canvas.AddText("Left", (x + w/4, y + h/4), + Position = "cc") + self.MovingRects.extend( (R,L) ) + + x += w/2 + R = Canvas.AddRectangle((x, y), (w/2, h/2), + LineWidth = 2, + FillColor = color) + + R.HitFill = True + R.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.RectMoveRight) + L = Canvas.AddText("Right", (x + w/4, y + h/4), + Position = "cc") + self.MovingRects.extend( (R,L) ) + + x -= w/2 + y += h/2 + R = Canvas.AddRectangle((x, y), (w/2, h/2), + LineWidth = 2, + FillColor = color) + R.HitFill = True + R.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.RectMoveUp) + L = Canvas.AddText("Up", (x + w/4, y + h/4), + Position = "cc") + self.MovingRects.extend( (R,L) ) + + + x += w/2 + R = Canvas.AddRectangle((x, y), (w/2, h/2), + LineWidth = 2, + FillColor = color) + R.HitFill = True + R.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.RectMoveDown) + L = Canvas.AddText("Down", (x + w/4, y + h/4), + Position = "cc") + self.MovingRects.extend( (R,L) ) + + self.Canvas.ZoomToBB() + + def RectMoveLeft(self,Object): + self.MoveRects("left") + + def RectMoveRight(self,Object): + self.MoveRects("right") + + def RectMoveUp(self,Object): + self.MoveRects("up") + + def RectMoveDown(self,Object): + self.MoveRects("down") + + def MoveRects(self, Dir): + for Object in self.MovingRects: + X,Y = Object.XY + if Dir == "left": X -= 10 + elif Dir == "right": X += 10 + elif Dir == "up": Y += 10 + elif Dir == "down": Y -= 10 + Object.SetPoint((X,Y)) + self.Canvas.Draw(True) + + def UnBindAllMouseEvents(self): + ## Here is how you catch FloatCanvas mouse events + FloatCanvas.EVT_LEFT_DOWN(self.Canvas, None ) + FloatCanvas.EVT_LEFT_UP(self.Canvas, None ) + FloatCanvas.EVT_LEFT_DCLICK(self.Canvas, None) + + FloatCanvas.EVT_MIDDLE_DOWN(self.Canvas, None ) + FloatCanvas.EVT_MIDDLE_UP(self.Canvas, None ) + FloatCanvas.EVT_MIDDLE_DCLICK(self.Canvas, None ) + + FloatCanvas.EVT_RIGHT_DOWN(self.Canvas, None ) + FloatCanvas.EVT_RIGHT_UP(self.Canvas, None ) + FloatCanvas.EVT_RIGHT_DCLICK(self.Canvas, None ) + + FloatCanvas.EVT_MOUSEWHEEL(self.Canvas, None ) + + self.EventsAreBound = False + +class DemoApp(wx.App): + """ + 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 + wx.Cursors 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. + + + Any bugs, comments, feedback, questions, and especially code are welcome: + + -Chris Barker + + Chris.Barker@noaa.gov + + """ + + def OnInit(self): + frame = DrawFrame(None, wx.ID_ANY, + title = "FloatCanvas Demo App", + size = (700,700) ) + + self.SetTopWindow(frame) + + return True + +app = DemoApp(False) +app.MainLoop() + + + + + + + + + + + + + diff --git a/samples/floatcanvas/MouseTest.py b/samples/floatcanvas/MouseTest.py new file mode 100755 index 00000000..7e344df6 --- /dev/null +++ b/samples/floatcanvas/MouseTest.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python + +""" +Small demo of catching Mouse events using just FloatCanvas, rather than +NavCanvas + +""" + +import wx + +app = wx.App(0) + +try: + # See if there is a local copy + import sys + sys.path.append("../") + from floatcanvas import NavCanvas, FloatCanvas, GUIMode +except ImportError: + from wx.lib.floatcanvas import NavCanvas, FloatCanvas, GUIMode + +class TestFrame(wx.Frame): + + def __init__(self, *args, **kwargs): + + wx.Frame.__init__(self, *args, **kwargs) + self.canvas =FloatCanvas.FloatCanvas(self, BackgroundColor = "DARK SLATE BLUE") + + # Layout + MainSizer = wx.BoxSizer(wx.VERTICAL) + MainSizer.Add(self.canvas, 4, wx.EXPAND) + self.SetSizer(MainSizer) + + self.canvas.Bind(FloatCanvas.EVT_LEFT_DOWN, self.OnLeftDown) + + self.canvas.AddRectangle((10,10), (100, 20), FillColor="red") + + self.canvas.SetMode(GUIMode.GUIMouse(self.canvas)) + + wx.CallAfter(self.canvas.ZoomToBB) + + def OnLeftDown(self, event): + print 'Left Button clicked at:', event.Coords + +frame = TestFrame(None, title="Mouse Event Tester") +#self.SetTopWindow(frame) +frame.Show(True) +app.MainLoop() + diff --git a/samples/floatcanvas/MovingElements.py b/samples/floatcanvas/MovingElements.py new file mode 100755 index 00000000..08f349e1 --- /dev/null +++ b/samples/floatcanvas/MovingElements.py @@ -0,0 +1,281 @@ +#!/usr/bin/env python +""" + +This is a small demo, showing how to make an object that can be moved around. + +It also contains a simple prototype for a "Connector" object + -- a line connecting two other objects + +""" + +import wx + +#ver = 'local' +ver = 'installed' + +if ver == 'installed': ## import the installed version + from wx.lib.floatcanvas import NavCanvas, Resources + from wx.lib.floatcanvas import FloatCanvas as FC + print "using installed version:", wx.lib.floatcanvas.__version__ +elif ver == 'local': + ## import a local version + import sys + sys.path.append("..") + from floatcanvas import NavCanvas, Resources + from floatcanvas import FloatCanvas as FC + from floatcanvas.Utilities import BBox + +import numpy as N + +## here we create some new mixins: + +class MovingObjectMixin: + """ + Methods required for a Moving object + + """ + + def GetOutlinePoints(self): + BB = self.BoundingBox + OutlinePoints = N.array( ( (BB[0,0], BB[0,1]), + (BB[0,0], BB[1,1]), + (BB[1,0], BB[1,1]), + (BB[1,0], BB[0,1]), + ) + ) + + return OutlinePoints + + +class ConnectorObjectMixin: + """ + Mixin class for DrawObjects that can be connected with lines + + NOte that this versionony works for Objects that have an "XY" attribute: + that is, one that is derived from XHObjectMixin. + + """ + + def GetConnectPoint(self): + return self.XY + +class MovingBitmap(FC.ScaledBitmap, MovingObjectMixin, ConnectorObjectMixin): + """ + ScaledBitmap Object that can be moved + """ + ## All we need to do is is inherit from: + ## ScaledBitmap, MovingObjectMixin and ConnectorObjectMixin + pass + +class MovingCircle(FC.Circle, MovingObjectMixin, ConnectorObjectMixin): + """ + ScaledBitmap Object that can be moved + """ + ## All we need to do is is inherit from: + ## ScaledBitmap, MovingObjectMixin and ConnectorObjectMixin + pass + +class MovingArc(FC.Arc, MovingObjectMixin, ConnectorObjectMixin): + """ + ScaledBitmap Object that can be moved + """ + ## All we need to do is is inherit from: + ## ScaledBitmap, MovingObjectMixin and ConnectorObjectMixin + pass + +class ConnectorLine(FC.LineOnlyMixin, FC.DrawObject,): + """ + + A Line that connects two objects -- it uses the objects to get its coordinates + + """ + ##fixme: this should be added to the Main FloatCanvas Objects some day. + def __init__(self, + Object1, + Object2, + LineColor = "Black", + LineStyle = "Solid", + LineWidth = 1, + InForeground = False): + FC.DrawObject.__init__(self, InForeground) + + self.Object1 = Object1 + self.Object2 = Object2 + self.LineColor = LineColor + self.LineStyle = LineStyle + self.LineWidth = LineWidth + + self.CalcBoundingBox() + self.SetPen(LineColor,LineStyle,LineWidth) + + self.HitLineWidth = max(LineWidth,self.MinHitLineWidth) + + def CalcBoundingBox(self): + self.BoundingBox = BBox.fromPoints((self.Object1.GetConnectPoint(), + self.Object2.GetConnectPoint()) ) + if self._Canvas: + self._Canvas.BoundingBoxDirty = True + + + def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None): + Points = N.array( (self.Object1.GetConnectPoint(), + self.Object2.GetConnectPoint()) ) + Points = WorldToPixel(Points) + dc.SetPen(self.Pen) + dc.DrawLines(Points) + if HTdc and self.HitAble: + HTdc.SetPen(self.HitPen) + HTdc.DrawLines(Points) + + +class TriangleShape1(FC.Polygon, MovingObjectMixin): + + def __init__(self, XY, L): + + """ + An equilateral triangle object + XY is the middle of the triangle + L is the length of one side of the Triangle + """ + + XY = N.asarray(XY) + XY.shape = (2,) + + Points = self.CompPoints(XY, L) + + FC.Polygon.__init__(self, Points, + LineColor = "Black", + LineStyle = "Solid", + LineWidth = 2, + FillColor = "Red", + FillStyle = "Solid") + ## Override the default OutlinePoints + def GetOutlinePoints(self): + return self.Points + + def CompPoints(self, XY, L): + c = L/ N.sqrt(3) + + Points = N.array(((0, c), + ( L/2.0, -c/2.0), + (-L/2.0, -c/2.0)), + N.float_) + + Points += XY + return Points + + +class DrawFrame(wx.Frame): + + """ + A simple frame used for the Demo + + """ + + def __init__(self, *args, **kwargs): + wx.Frame.__init__(self, *args, **kwargs) + + self.CreateStatusBar() + # Add the Canvas + Canvas = NavCanvas.NavCanvas(self,-1,(500,500), + ProjectionFun = None, + Debug = 0, + BackgroundColor = "DARK SLATE BLUE", + ).Canvas + + self.Canvas = Canvas + + Canvas.Bind(FC.EVT_MOTION, self.OnMove ) + Canvas.Bind(FC.EVT_LEFT_UP, self.OnLeftUp ) + + Points = N.array(((0,0), + (1,0), + (0.5, 1)), + N.float) + + data = (( (0,0), 1), + ( (3,3), 2), + ( (-2,3), 2.5 ), + ) + + for p, L in data: + Tri = TriangleShape1(p, 1) + Canvas.AddObject(Tri) + Tri.Bind(FC.EVT_FC_LEFT_DOWN, self.ObjectHit) + + Circle = MovingCircle( (1, 3), 2, FillColor="Blue") + Canvas.AddObject(Circle) + Circle.Bind(FC.EVT_FC_LEFT_DOWN, self.ObjectHit) + + Bitmaps = [] + ## create the bitmaps first + for Point in ((1,1), (-4,3)): + Bitmaps.append(MovingBitmap(Resources.getMondrianImage(), + Point, + Height=1, + Position='cc') + ) + Line = ConnectorLine(Bitmaps[0], Bitmaps[1], LineWidth=3, LineColor="Red") + Canvas.AddObject(Line) + + + + ## then add them to the Canvas, so they are on top of the line + for bmp in Bitmaps: + Canvas.AddObject(bmp) + bmp.Bind(FC.EVT_FC_LEFT_DOWN, self.ObjectHit) + + A = MovingArc((-5, 0),(-2, 2),(-5, 2), LineColor="Red", LineWidth=2) + self.Canvas.AddObject(A) + A.Bind(FC.EVT_FC_LEFT_DOWN, self.ObjectHit) + + self.Show(True) + self.Canvas.ZoomToBB() + + self.MoveObject = None + self.Moving = False + + return None + + def ObjectHit(self, object): + if not self.Moving: + self.Moving = True + self.StartPoint = object.HitCoordsPixel + self.StartObject = self.Canvas.WorldToPixel(object.GetOutlinePoints()) + self.MoveObject = None + self.MovingObject = object + + def OnMove(self, event): + """ + Updates the status bar with the world coordinates + and moves the object it is clicked on + + """ + self.SetStatusText("%.4f, %.4f"%tuple(event.Coords)) + + if self.Moving: + dxy = event.GetPosition() - self.StartPoint + # Draw the Moving Object: + dc = wx.ClientDC(self.Canvas) + dc.SetPen(wx.Pen('WHITE', 2, wx.SHORT_DASH)) + dc.SetBrush(wx.TRANSPARENT_BRUSH) + dc.SetLogicalFunction(wx.XOR) + if self.MoveObject is not None: + dc.DrawPolygon(self.MoveObject) + self.MoveObject = self.StartObject + dxy + dc.DrawPolygon(self.MoveObject) + + def OnLeftUp(self, event): + if self.Moving: + self.Moving = False + if self.MoveObject is not None: + dxy = event.GetPosition() - self.StartPoint + dxy = self.Canvas.ScalePixelToWorld(dxy) + self.MovingObject.Move(dxy) + self.MoveTri = None + self.Canvas.Draw(True) + +if __name__ == "__main__": + app = wx.PySimpleApp(0) + DrawFrame(None, -1, "FloatCanvas Moving Object App", wx.DefaultPosition, (700,700) ) + app.MainLoop() diff --git a/samples/floatcanvas/MovingPlot.py b/samples/floatcanvas/MovingPlot.py new file mode 100755 index 00000000..bebc795e --- /dev/null +++ b/samples/floatcanvas/MovingPlot.py @@ -0,0 +1,238 @@ +#!/usr/bin/env python +""" + +A small test app that uses FloatCanvas to draw a plot, and have an +efficient moving line on it. + +""" + +import wx +import numpy as N + +## import the installed version +from wx.lib.floatcanvas import NavCanvas, FloatCanvas + +## import a local version +#import sys +#sys.path.append("../") +#from floatcanvas import NavCanvas, FloatCanvas + + +class DrawFrame(wx.Frame): + def __init__(self, *args, **kwargs): + wx.Frame.__init__(self, *args, **kwargs) + + ## Set up the MenuBar + + MenuBar = wx.MenuBar() + + file_menu = wx.Menu() + item = file_menu.Append(wx.ID_ANY, "E&xit","Terminate the program") + self.Bind(wx.EVT_MENU, self.OnQuit, item) + MenuBar.Append(file_menu, "&File") + + draw_menu = wx.Menu() + item = draw_menu.Append(wx.ID_ANY, "&Run","Run the test") + self.Bind(wx.EVT_MENU, self.RunTest, item) + item = draw_menu.Append(wx.ID_ANY, "&Stop","Stop the test") + self.Bind(wx.EVT_MENU, self.Stop, item) + MenuBar.Append(draw_menu, "&Plot") + + + help_menu = wx.Menu() + item = help_menu.Append(wx.ID_ANY, "&About", + "More information About this program") + self.Bind(wx.EVT_MENU, self.OnAbout, item) + MenuBar.Append(help_menu, "&Help") + + self.SetMenuBar(MenuBar) + + + self.CreateStatusBar() + self.SetStatusText("") + + wx.EVT_CLOSE(self, self.OnCloseWindow) + + # Add the Canvas + NC = NavCanvas.NavCanvas(self ,wx.ID_ANY ,(500,300), + ProjectionFun = None, + Debug = 0, + BackgroundColor = "WHITE" + ) + self.Canvas = NC.Canvas + + self.Canvas.NumBetweenBlits = 1000 + + # Add a couple of tools to the Canvas Toolbar + + tb = NC.ToolBar + tb.AddSeparator() + + StopButton = wx.Button(tb, wx.ID_ANY, "Stop") + tb.AddControl(StopButton) + StopButton.Bind(wx.EVT_BUTTON, self.Stop) + + PlayButton = wx.Button(tb, wx.ID_ANY, "Run") + tb.AddControl(PlayButton) + PlayButton.Bind(wx.EVT_BUTTON, self.RunTest) + + tb.Realize() + + self.Show(True) + + self.timer = None + + self.DrawAxis() + + return None + + + def OnAbout(self, event): + dlg = wx.MessageDialog(self, "This is a small program to demonstrate\n" + "the use of the FloatCanvas\n" + "for simple plotting", + "About Me", wx.OK | wx.ICON_INFORMATION) + dlg.ShowModal() + dlg.Destroy() + + def ZoomToFit(self,event): + self.Canvas.ZoomToBB() + + def OnQuit(self,event): + self.Close(True) + + def OnCloseWindow(self, event): + self.Destroy() + + def DrawAxis(self): + Canvas = self.Canvas + + # Draw the Axis + + # Note: the AddRectangle Parameters all have sensible + # defaults. I've put them all here explicitly, so you can see + # what the options are. + + self.Canvas.AddRectangle((0, -1.1), + (2*N.pi, 2.2), + LineColor = "Black", + LineStyle = "Solid", + LineWidth = 1, + FillColor = None, + FillStyle = "Solid", + InForeground = 0) + for tic in N.arange(7): + self.Canvas.AddText("%1.1f"%tic, + (tic,-1.1), + Position = 'tc') + + for tic in N.arange(-1, 1.1, 0.5): + self.Canvas.AddText("%1.1f"%tic, + (0,tic), + Position = 'cr') + + # Add a phantom rectangle to get the bounding box right + # (the bounding box doesn't get unscaled text right) + self.Canvas.AddRectangle((-0.7, -1.5), (7, 3), LineColor = None) + + Canvas.ZoomToBB() + Canvas.Draw() + + def Stop(self,event): + if self.timer: + self.timer.Stop() + + + def OnTimer(self,event): + self.count += .1 + self.data1[:,1] = N.sin(self.time+self.count) #fake move + + self.line.SetPoints(self.data1) + + self.Canvas.Draw() + + def RunTest(self,event = None): + self.n = 100 + self.dT = 0.05 + + self.time = 2.0*N.pi*N.arange(100)/100.0 + + self.data1 = 1.0*N.ones((100,2)) + self.data1[:,0] = self.time + self.data1[:,1] = N.sin(self.time) + Canvas = self.Canvas + self.Canvas.ClearAll() + self.DrawAxis() + self.line = Canvas.AddLine(self.data1, + LineColor = "Red", + LineStyle = "Solid", + LineWidth = 2, + InForeground = 1) + self.Canvas.Draw() + + self.timerID = wx.NewId() + self.timer = wx.Timer(self,self.timerID) + + wx.EVT_TIMER(self,self.timerID,self.OnTimer) + + self.count = 0 + self.timer.Start(int(self.dT*1000)) + +class DemoApp(wx.App): + """ + How the demo works: + + Either under the Draw menu, or on the toolbar, you can push Run and Stop + + "Run" start an oscilloscope like display of a moving sine curve + "Stop" stops it. + + While the plot os running (or not) 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. + + 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, but you can see the outline of the former picture. + + I'd like the cursor to change as you change tools, but the stock + wx.Cursors 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. + + + Any bugs, comments, feedback, questions, and especially code are welcome: + + -Chris Barker + + Chris.Barker@noaa.gov + + """ + + def OnInit(self): + wx.InitAllImageHandlers() + frame = DrawFrame(None, wx.ID_ANY, + title = "Plotting Test", + size = (700,400) ) + + self.SetTopWindow(frame) + + return True + + +if __name__ == "__main__": + + app = DemoApp(0) + app.MainLoop() + + diff --git a/samples/floatcanvas/MovingTriangle.py b/samples/floatcanvas/MovingTriangle.py new file mode 100755 index 00000000..882bd3d2 --- /dev/null +++ b/samples/floatcanvas/MovingTriangle.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python +""" + +This is a small demo, showing how to make an object that can be moved around. + +""" + +import wx + +#ver = 'local' +ver = 'installed' + +if ver == 'installed': ## import the installed version + from wx.lib.floatcanvas import NavCanvas, Resources + from wx.lib.floatcanvas import FloatCanvas as FC + print "using installed version:", wx.lib.floatcanvas.__version__ +elif ver == 'local': + ## import a local version + import sys + sys.path.append("..") + from floatcanvas import NavCanvas, Resources + from floatcanvas import FloatCanvas as FC + +import numpy as N + +## here we create a new DrawObject: +## code borrowed and adapted from Werner Bruhin + +class ShapeMixin: + """ + just here for added features later + """ + def __init__(self): + pass + +class TriangleShape1(FC.Polygon, ShapeMixin): + + def __init__(self, XY, L): + + """ + An equilateral triangle object + + XY is the middle of the triangle + + L is the length of one side of the Triangle + """ + + XY = N.asarray(XY) + XY.shape = (2,) + + Points = self.CompPoints(XY, L) + + FC.Polygon.__init__(self, Points, + LineColor = "Black", + LineStyle = "Solid", + LineWidth = 2, + FillColor = "Red", + FillStyle = "Solid") + ShapeMixin.__init__(self) + + def CompPoints(self, XY, L): + c = L/ N.sqrt(3) + + Points = N.array(((0, c), + ( L/2.0, -c/2.0), + (-L/2.0, -c/2.0)), + N.float_) + + Points += XY + return Points + + +class DrawFrame(wx.Frame): + + """ + A frame used for the FloatCanvas Demo + + """ + + def __init__(self,parent, id,title,position,size): + wx.Frame.__init__(self,parent, id,title,position, size) + + self.CreateStatusBar() + # Add the Canvas + Canvas = NavCanvas.NavCanvas(self,-1,(500,500), + ProjectionFun = None, + Debug = 0, + BackgroundColor = "DARK SLATE BLUE", + ).Canvas + + self.Canvas = Canvas + + Canvas.Bind(FC.EVT_MOTION, self.OnMove ) + Canvas.Bind(FC.EVT_LEFT_UP, self.OnLeftUp ) + + Canvas.AddRectangle((-5,-5), + (10,10), + LineColor = "Red", + LineStyle = "Solid", + LineWidth = 2, + FillColor = "CYAN", + FillStyle = "Solid") + + Points = N.array(((0,0), + (1,0), + (0.5, 1)), + N.float_) + + data = (( (0,0), 1), + ( (3,3), 2), + ( (-2,3), 2.5 ), + ) + + for p, L in data: + Tri = TriangleShape1(p, 1) + Canvas.AddObject(Tri) + Tri.Bind(FC.EVT_FC_LEFT_DOWN, self.TriHit) + + + self.MoveTri = None + + self.Show(True) + self.Canvas.ZoomToBB() + + self.Moving = False + + return None + + def TriHit(self, object): + print "In TriHit" + if not self.Moving: + self.Moving = True + self.StartPoint = object.HitCoordsPixel + self.StartTri = self.Canvas.WorldToPixel(object.Points) + self.MoveTri = None + self.MovingTri = object + + def OnMove(self, event): + """ + Updates the status bar with the world coordinates + + And moves the triangle it it is clicked on + + """ + self.SetStatusText("%.4f, %.4f"%tuple(event.Coords)) + + if self.Moving: + dxy = event.GetPosition() - self.StartPoint + # Draw the Moving Triangle: + dc = wx.ClientDC(self.Canvas) + dc.SetPen(wx.Pen('WHITE', 2, wx.SHORT_DASH)) + dc.SetBrush(wx.TRANSPARENT_BRUSH) + dc.SetLogicalFunction(wx.XOR) + if self.MoveTri is not None: + dc.DrawPolygon(self.MoveTri) + self.MoveTri = self.StartTri + dxy + dc.DrawPolygon(self.MoveTri) + + def OnLeftUp(self, event): + if self.Moving: + self.Moving = False + if self.MoveTri is not None: + dxy = event.GetPosition() - self.StartPoint + dxy = self.Canvas.ScalePixelToWorld(dxy) + self.MovingTri.Move(dxy) ## The Move function has jsut been added + ## to the FloatCanvas PointsObject + ## It does the next three lines for you. + #self.Tri.Points += dxy + #self.Tri.BoundingBox += dxy + #self.Canvas.BoundingBoxDirty = True + self.MoveTri = None + self.Canvas.Draw(True) + +app = wx.PySimpleApp(0) +DrawFrame(None, -1, "FloatCanvas TextBox Test App", wx.DefaultPosition, (700,700) ) +app.MainLoop() + + + + + + + + + + + + + diff --git a/samples/floatcanvas/NOAA.png b/samples/floatcanvas/NOAA.png new file mode 100644 index 0000000000000000000000000000000000000000..f4d0e5537861fe47c4ea28e12b8f7939bb7ce09c GIT binary patch literal 5510 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 IM6N<$g0bb9wEzGB literal 0 HcmV?d00001 diff --git a/samples/floatcanvas/NoToolbar.py b/samples/floatcanvas/NoToolbar.py new file mode 100755 index 00000000..b8d35972 --- /dev/null +++ b/samples/floatcanvas/NoToolbar.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python + +""" +A simple example of how to use FloatCanvas by itself, without the NavCanvas toolbar + +""" + +import wx + +## import the installed version +from wx.lib.floatcanvas import NavCanvas, FloatCanvas + +## import a local version +#import sys +#sys.path.append("../") +#from floatcanvas import NavCanvas, FloatCanvas + + +class DrawFrame(wx.Frame): + + """ + A frame used for the FloatCanvas Demo + + """ + + def __init__(self, *args, **kwargs): + wx.Frame.__init__(self, *args, **kwargs) + + self.CreateStatusBar() + + # Add the Canvas + Canvas = FloatCanvas.FloatCanvas(self, + size = (500,500), + BackgroundColor = "DARK SLATE BLUE", + ) + + self.Canvas = Canvas + + FloatCanvas.EVT_MOTION(self.Canvas, self.OnMove ) + + Point = (45,40) + Box = Canvas.AddScaledTextBox("A Two Line\nString", + Point, + 2, + Color = "Black", + BackgroundColor = None, + LineColor = "Red", + LineStyle = "Solid", + LineWidth = 1, + Width = None, + PadSize = 5, + Family = wx.ROMAN, + Style = wx.NORMAL, + Weight = wx.NORMAL, + Underlined = False, + Position = 'br', + Alignment = "left", + InForeground = False) + + Box.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.Binding) + self.Show() + Canvas.ZoomToBB() + + def OnMove(self, event): + """ + Updates the status bar with the world coordinates + + """ + self.SetStatusText("%.2f, %.2f"%tuple(event.Coords)) + + def Binding(self, event): + print "Writing a png file:" + self.Canvas.SaveAsImage("junk.png") + print "Writing a jpeg file:" + self.Canvas.SaveAsImage("junk.jpg",wx.BITMAP_TYPE_JPEG) + + +app = wx.App(False) +F = DrawFrame(None, title="FloatCanvas Demo App", size=(700,700) ) +app.MainLoop() + + + + + + + + + + + + + diff --git a/samples/floatcanvas/NonGUI.py b/samples/floatcanvas/NonGUI.py new file mode 100755 index 00000000..de1c9b6a --- /dev/null +++ b/samples/floatcanvas/NonGUI.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python + +""" +Demo of a FloatCanvas App that will create an image, without +actually showing anything on screen. It seems to work, but you do need +to have an X-server running on *nix for this to work. + +Note: you need to specify the size in the FloatCanvas Constructor. + +hmm -- I wonder if you'd even need the frame? + +""" + +import wx + +## import the installed version +from wx.lib.floatcanvas import FloatCanvas + +## import a local version +#import sys +#sys.path.append("../") +#from floatcanvas import NavCanvas, FloatCanvas + +app = wx.PySimpleApp() + +f = wx.Frame(None) +Canvas = FloatCanvas.FloatCanvas(f, + BackgroundColor = "Cyan", + size=(500,500) + ) + + +Canvas.AddRectangle((0,0), (16,20)) +Canvas.AddRectangle((1,1), (6,8.5)) +Canvas.AddRectangle((9,1), (6,8.5)) +Canvas.AddRectangle((9,10.5), (6,8.5)) +Canvas.AddRectangle((1,10.5), (6,8.5)) +Canvas.ZoomToBB() + + +print "Saving the image:", "junk.png" +Canvas.SaveAsImage("junk.png") + diff --git a/samples/floatcanvas/OverlayDemo.py b/samples/floatcanvas/OverlayDemo.py new file mode 100755 index 00000000..2c2e9ac0 --- /dev/null +++ b/samples/floatcanvas/OverlayDemo.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python + +""" +A simple demo to show how to "Overlays": i.e. drawing something +on top of eveything else on the Canvas, relative to window coords, +rather than screen coords. + +This method uses the "GridOver" object in the FloatCanvas + - it was orginally dsigend for girds, graticule,s etc. that + are always drawn regardless of zoom, pan, etc, but it works + for overlays too. + +""" + +import wx + + +try: + # See if there is a local copy + import sys + sys.path.append("../") + from floatcanvas import NavCanvas, FloatCanvas +except ImportError: + from wx.lib.floatcanvas import NavCanvas, FloatCanvas + +class TextOverlay(FloatCanvas.Text): + """ + An example of an Overlay object: + + all it needs is a new _Draw method. + + NOTE: you may want to get fancier with this, + deriving from ScaledTextBox + + """ + def __init__(self, + String, + xy, + Size = 24, + Color = "Black", + BackgroundColor = None, + Family = wx.MODERN, + Style = wx.NORMAL, + Weight = wx.NORMAL, + Underlined = False, + Font = None): + FloatCanvas.Text.__init__(self, + String, + xy, + Size = Size, + Color = Color, + BackgroundColor = BackgroundColor, + Family = Family, + Style = Style, + Weight = Weight, + Underlined = Underlined, + Font = Font) + + def _Draw(self, dc, Canvas): + """ + _Draw method for Overlay + note: this is a differeent signarture than the DrawObject Draw + """ + dc.SetFont(self.Font) + dc.SetTextForeground(self.Color) + if self.BackgroundColor: + dc.SetBackgroundMode(wx.SOLID) + dc.SetTextBackground(self.BackgroundColor) + else: + dc.SetBackgroundMode(wx.TRANSPARENT) + ## maybe inpliment this... + #if self.TextWidth is None or self.TextHeight is None: + # (self.TextWidth, self.TextHeight) = dc.GetTextExtent(self.String) + #XY = self.ShiftFun(XY[0], XY[1], self.TextWidth, self.TextHeight) + dc.DrawTextPoint(self.String, self.XY) + + +class DrawFrame(wx.Frame): + + """ + A frame used for the FloatCanvas Demo + + """ + + def __init__(self, *args, **kwargs): + wx.Frame.__init__(self, *args, **kwargs) + + self.CreateStatusBar() + + # Add the Canvas + Canvas = NavCanvas.NavCanvas(self,-1, + size = (500,500), + ProjectionFun = None, + Debug = 0, + BackgroundColor = "DARK SLATE BLUE", + ).Canvas + + + Point = (45,40) + Box = Canvas.AddCircle(Point, + Diameter = 10, + FillColor = "Cyan", + LineColor = "Red", + LineWidth = 6) + + Canvas.GridOver = TextOverlay("Some Text", + (20,20), + Size = 48, + Color = "Black", + BackgroundColor = 'Pink', + ) + + FloatCanvas.EVT_MOTION(Canvas, self.OnMove ) + + self.Show() + Canvas.ZoomToBB() + + def OnMove(self, event): + """ + Updates the status bar with the world coordinates + + """ + self.SetStatusText("%.2f, %.2f"%tuple(event.Coords)) + + + +app = wx.App(False) # true to get its own output window. +F = DrawFrame(None, title="FloatCanvas Demo App", size=(700,700) ) +app.MainLoop() + + + + + + + + + + + + + diff --git a/samples/floatcanvas/PieChart.py b/samples/floatcanvas/PieChart.py new file mode 100755 index 00000000..b0a6d0c6 --- /dev/null +++ b/samples/floatcanvas/PieChart.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python + + +import wx + +## import the installed version +from wx.lib.floatcanvas import NavCanvas, FloatCanvas +from wx.lib.floatcanvas.SpecialObjects import PieChart + +## import a local version +#import sys +#sys.path.append("..") +#from floatcanvas import NavCanvas, FloatCanvas +#from floatcanvas.SpecialObjects import PieChart + + +import numpy as N + +class DrawFrame(wx.Frame): + + """ + A frame used for the FloatCanvas Demo + + """ + + def __init__(self, *args, **kwargs): + wx.Frame.__init__(self, *args, **kwargs) + + self.CreateStatusBar() + + # Add the Canvas + Canvas = NavCanvas.NavCanvas(self,-1, + size = (500,500), + Debug = 0, + BackgroundColor = "DARK SLATE BLUE", + ).Canvas + + self.Canvas = Canvas + + Values = (10,10,10) + Colors = ('Red', 'Blue', 'Green') + Pie1 = PieChart(N.array((0, 0)), 10, Values, Colors, Scaled=False) + Canvas.AddObject(Pie1) + + Values = (10, 5, 5) + Pie2 = PieChart(N.array((40, 0)), 10, Values, Colors) + Canvas.AddObject(Pie2) + + # test default colors + Values = (10, 15, 12, 24, 6, 10, 13, 11, 9, 13, 15, 12) + Pie3 = PieChart(N.array((20, 20)), 10, Values, LineColor="Black") + Canvas.AddObject(Pie3) + + # missng slice! + Values = (10, 15, 12, 24) + Colors = ('Red', 'Blue', 'Green', None) + Pie4 = PieChart(N.array((0, -15)), 10, Values, Colors, LineColor="Black") + Canvas.AddObject(Pie4) + + + # Test the styles + Values = (10, 12, 14) + Styles = ("Solid", "CrossDiagHatch","CrossHatch") + Colors = ('Red', 'Blue', 'Green') + Pie4 = PieChart(N.array((20, -20)), 10, Values, Colors, Styles) + Canvas.AddObject(Pie2) + + Pie1.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.Pie1Hit) + Pie2.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.Pie2Hit) + + FloatCanvas.EVT_MOTION(self.Canvas, self.OnMove ) + + self.Show() + Canvas.ZoomToBB() + + def Pie1Hit(self, obj): + print "Pie1 hit!" + + def Pie2Hit(self, obj): + print "Pie2 hit!" + + def OnMove(self, event): + """ + Updates the status bar with the world coordinates + """ + self.SetStatusText("%.2g, %.2g"%tuple(event.Coords)) + + +app = wx.App(False) +F = DrawFrame(None, title="FloatCanvas Demo App", size=(700,700) ) +app.MainLoop() diff --git a/samples/floatcanvas/PixelBitmap.py b/samples/floatcanvas/PixelBitmap.py new file mode 100755 index 00000000..2c2d57c6 --- /dev/null +++ b/samples/floatcanvas/PixelBitmap.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python + +""" +A simple demo to show how to drw a bitmap on top of the Canvas at given pixel coords. + +""" + +import wx +import numpy as np + +try: + # See if there is a local copy + import sys + sys.path.append("../") + from floatcanvas import NavCanvas, FloatCanvas +except ImportError: + from wx.lib.floatcanvas import NavCanvas, FloatCanvas +FC = FloatCanvas + +class PixelBitmap: + """ + An unscaled bitmap that can be put on top of the canvas using: + + Canvas.GridOver = MyPixelBitmap + + It will always be drawn on top of everything else, and be positioned + according to pixel coordinates on teh screen, regardless of zoom and + pan position. + + """ + def __init__(self, Bitmap, XY, Position = 'tl'): + """ + PixelBitmap (Bitmap, XY, Position='tl') + + Bitmap is a wx.Bitmap or wx.Image + + XY is the (x,y) location to place the bitmap, in pixel coordinates + + Position indicates from where in the window the position is relative to: + 'tl' indicated the position from the top left the the window (the detault) + 'br' the bottom right + 'cr the center right, etc. + + """ + if type(Bitmap) == wx._gdi.Bitmap: + self.Bitmap = Bitmap + elif type(Bitmap) == wx._core.Image: + self.Bitmap = wx.BitmapFromImage(Bitmap) + else: + raise FC.FloatCanvasError("PixelBitmap takes only a wx.Bitmap or a wx.Image as input") + + self.XY = np.asarray(XY, dtype=np.int).reshape((2,)) + self.Position = Position + + (self.Width, self.Height) = self.Bitmap.GetWidth(), self.Bitmap.GetHeight() + self.ShiftFun = FC.TextObjectMixin.ShiftFunDict[Position] + + def _Draw(self, dc, Canvas): + w, h = Canvas.Size + XY = self.XY + if self.Position[0] == 'b': + XY = (XY[0], h - XY[1] - self.Height) + elif self.Position[0] == 'c': + XY = (XY[0], XY[1] + (h - self.Height)/2) + + if self.Position[1] == 'r': + XY = (w - XY[0] - self.Width, XY[1]) + elif self.Position[1] == 'c': + XY = (XY[0] + (w - self.Width)/2, XY[1]) + + dc.DrawBitmapPoint(self.Bitmap, XY, True) + +class GridGroup: + def __init__(self, grids=[]): + self.Grids = grids + + def _Draw(self, *args): + for grid in self.Grids: + grid._Draw(*args) + +class DrawFrame(wx.Frame): + + """ + A frame used for the FloatCanvas Demo + + """ + + def __init__(self, *args, **kwargs): + wx.Frame.__init__(self, *args, **kwargs) + + self.CreateStatusBar() + + # Add the Canvas + Canvas = NavCanvas.NavCanvas(self,-1, + size = (500,500), + ProjectionFun = None, + Debug = 0, + BackgroundColor = "DARK SLATE BLUE", + ).Canvas + + + Point = (45,40) + Box = Canvas.AddCircle(Point, + Diameter = 10, + FillColor = "Black", + LineColor = "Red", + LineWidth = 6) + bmp = wx.Bitmap('NOAA.png') + grids = GridGroup([PixelBitmap(bmp, (10, 10), Position='tl'), + PixelBitmap(bmp, (10, 10), Position='br'), + PixelBitmap(bmp, (10, 10), Position='tr'), + PixelBitmap(bmp, (10, 10), Position='bl'), + PixelBitmap(bmp, (10, 10), Position='cl'), + PixelBitmap(bmp, (10, 10), Position='cr'), + PixelBitmap(bmp, (10, 10), Position='cc'), + PixelBitmap(bmp, (10, 10), Position='tc'), + PixelBitmap(bmp, (10, 10), Position='bc'), + ]) + Canvas.GridOver = grids + + FloatCanvas.EVT_MOTION(Canvas, self.OnMove ) + + self.Show() + Canvas.ZoomToBB() + + def OnMove(self, event): + """ + Updates the status bar with the world coordinates + + """ + self.SetStatusText("%.2f, %.2f"%tuple(event.Coords)) + + + +app = wx.App(False) # true to get its own output window. +F = DrawFrame(None, title="FloatCanvas Demo App", size=(700,700) ) +app.MainLoop() + + + + + + + + + + + + + diff --git a/samples/floatcanvas/PointsHitDemo.py b/samples/floatcanvas/PointsHitDemo.py new file mode 100755 index 00000000..0a44925d --- /dev/null +++ b/samples/floatcanvas/PointsHitDemo.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python + +import wx + +## import the installed version +from wx.lib.floatcanvas import NavCanvas, FloatCanvas + +## import a local version +#import sys +#sys.path.append("../") +#from floatcanvas import NavCanvas, FloatCanvas + + +class DrawFrame(wx.Frame): + + """ + A frame used for the FloatCanvas Demo + + """ + + def __init__(self, *args, **kwargs): + wx.Frame.__init__(self, *args, **kwargs) + + self.CreateStatusBar() + + # Add the Canvas + Canvas = NavCanvas.NavCanvas(self,-1, + size = (500,500), + ProjectionFun = None, + Debug = 0, + BackgroundColor = "DARK SLATE BLUE", + ).Canvas + + self.Canvas = Canvas + + FloatCanvas.EVT_MOTION(self.Canvas, self.OnMove ) + + Pts = ((45,40), (20, 15), (10, 40), (30,30)) + + Points = Canvas.AddPointSet(Pts, Diameter=3, Color="Red") + Points.HitLineWidth = 10 + + Points.Bind(FloatCanvas.EVT_FC_ENTER_OBJECT, self.OnOverPoints) + Points.Bind(FloatCanvas.EVT_FC_LEAVE_OBJECT, self.OnLeavePoints) + Points.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.OnLeftDown) + + self.Show() + Canvas.ZoomToBB() + + def OnOverPoints(self, obj): + print "Mouse over point: ", obj.FindClosestPoint(obj.HitCoords) + + def OnLeavePoints(self, obj): + print "Mouse left point: ", obj.FindClosestPoint(obj.HitCoords) + + def OnLeftDown(self, obj): + print "Mouse left down on point: ", obj.FindClosestPoint(obj.HitCoords) + + def OnMove(self, event): + """ + Updates the status bar with the world coordinates + + """ + self.SetStatusText("%.2f, %.2f"%tuple(event.Coords)) + +app = wx.App(False) +F = DrawFrame(None, title="FloatCanvas Demo App", size=(700,700) ) +app.MainLoop() + + + + + + + + + + + + + diff --git a/samples/floatcanvas/PolyEditor.py b/samples/floatcanvas/PolyEditor.py new file mode 100755 index 00000000..af545393 --- /dev/null +++ b/samples/floatcanvas/PolyEditor.py @@ -0,0 +1,255 @@ +#!/usr/bin/env python + +""" +PolyEditor: a simple app for editing polygons + +Used as a demo for FloatCanvas +""" +import numpy as N +import random +import numpy.random as RandomArray + +from wx.lib.floatcanvas import NavCanvas, FloatCanvas + +# import a local copy: +#import sys +#sys.path.append("..") +#from floatcanvas import NavCanvas, FloatCanvas + +import wx + +ID_ABOUT_MENU = wx.ID_ABOUT +ID_ZOOM_TO_FIT_MENU = wx.NewId() + +class DrawFrame(wx.Frame): + + """ + A frame used for the FloatCanvas Demo + + """ + + def __init__(self,parent, id,title,position,size): + wx.Frame.__init__(self,parent, id,title,position, size) + + ## Set up the MenuBar + MenuBar = wx.MenuBar() + + FileMenu = wx.Menu() + FileMenu.Append(wx.NewId(), "&Close","Close Application") + wx.EVT_MENU(self, FileMenu.FindItem("Close"), self.OnQuit) + + MenuBar.Append(FileMenu, "&File") + + view_menu = wx.Menu() + view_menu.Append(wx.NewId(), "Zoom to &Fit","Zoom to fit the window") + wx.EVT_MENU(self, view_menu.FindItem("Zoom to &Fit"), self.ZoomToFit) + MenuBar.Append(view_menu, "&View") + + help_menu = wx.Menu() + help_menu.Append(ID_ABOUT_MENU, "&About", + "More information About this program") + wx.EVT_MENU(self, ID_ABOUT_MENU, self.OnAbout) + MenuBar.Append(help_menu, "&Help") + + self.SetMenuBar(MenuBar) + + self.CreateStatusBar() + # Add the Canvas + self.Canvas = NavCanvas.NavCanvas(self,-1,(500,500), + Debug = 0, + BackgroundColor = "DARK SLATE BLUE" + ).Canvas + + wx.EVT_CLOSE(self, self.OnCloseWindow) + + FloatCanvas.EVT_MOTION(self.Canvas, self.OnMove ) + FloatCanvas.EVT_LEFT_UP(self.Canvas, self.OnLeftUp ) + FloatCanvas.EVT_LEFT_DOWN(self.Canvas, self.OnLeftClick ) + + self.ResetSelections() + + return None + + def ResetSelections(self): + self.SelectedPoly = None + self.SelectedPolyOrig = None + self.SelectedPoints = None + self.PointSelected = False + self.SelectedPointNeighbors = None + + def OnAbout(self, event): + dlg = wx.MessageDialog(self, "This is a small program to demonstrate\n" + "the use of the FloatCanvas\n", + "About Me", wx.OK | wx.ICON_INFORMATION) + dlg.ShowModal() + dlg.Destroy() + + def ZoomToFit(self,event): + self.Canvas.ZoomToBB() + + def Clear(self,event = None): + self.Canvas.ClearAll() + self.Canvas.SetProjectionFun(None) + self.Canvas.Draw() + + def OnQuit(self,event): + self.Close(True) + + def OnCloseWindow(self, event): + self.Destroy() + + def OnMove(self, event): + """ + Updates the status bar with the world coordinates + + And moves a point if there is one selected + + """ + self.SetStatusText("%.2f, %.2f"%tuple(event.Coords)) + + if self.PointSelected: + PolyPoints = self.SelectedPoly.Points + Index = self.SelectedPoints.Index + dc = wx.ClientDC(self.Canvas) + PixelCoords = event.GetPosition() + dc.SetPen(wx.Pen('WHITE', 2, wx.SHORT_DASH)) + dc.SetLogicalFunction(wx.XOR) + if self.SelectedPointNeighbors is None: + self.SelectedPointNeighbors = N.zeros((3,2), N.float_) + #fixme: This feels very inelegant! + if Index == 0: + self.SelectedPointNeighbors[0] = self.SelectedPoly.Points[-1] + self.SelectedPointNeighbors[1:3] = self.SelectedPoly.Points[:2] + elif Index == len(self.SelectedPoly.Points)-1: + self.SelectedPointNeighbors[0:2] = self.SelectedPoly.Points[-2:] + self.SelectedPointNeighbors[2] = self.SelectedPoly.Points[0] + else: + self.SelectedPointNeighbors = self.SelectedPoly.Points[Index-1:Index+2] + self.SelectedPointNeighbors = self.Canvas.WorldToPixel(self.SelectedPointNeighbors) + else: + dc.DrawLines(self.SelectedPointNeighbors) + self.SelectedPointNeighbors[1] = PixelCoords + dc.DrawLines(self.SelectedPointNeighbors) + + + def OnLeftUp(self, event): + ## if a point was selected, it's not anymore + if self.PointSelected: + self.SelectedPoly.Points[self.SelectedPoints.Index] = event.GetCoords() + self.SelectedPoly.SetPoints(self.SelectedPoly.Points, copy = False) + self.SelectedPoints.SetPoints(self.SelectedPoly.Points, copy = False) + self.PointSelected = False + self.SelectedPointNeighbors = None + self.Canvas.Draw() + + def OnLeftClick(self,event): + ## If a click happens outside the polygon, it's no longer selected + self.DeSelectPoly() + self.Canvas.Draw() + + def Setup(self, event = None): + "Seting up with some random polygons" + wx.GetApp().Yield() + self.ResetSelections() + self.Canvas.ClearAll() + + Range = (-10,10) + + # Create a couple of random Polygons + for i, color in enumerate(("LightBlue", "Green", "Purple","Yellow")): + points = RandomArray.uniform(Range[0],Range[1],(6,2)) + Poly = self.Canvas.AddPolygon(points, + LineWidth = 2, + LineColor = "Black", + FillColor = color, + FillStyle = 'Solid') + + Poly.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.SelectPoly) + self.Canvas.ZoomToBB() + + + def SelectPoly(self, Object): + Canvas = self.Canvas + if Object is self.SelectedPolyOrig: + pass + else: + if self.SelectedPoly: + self.DeSelectPoly() + self.SelectedPolyOrig = Object + self.SelectedPoly = Canvas.AddPolygon(Object.Points, + LineWidth = 2, + LineColor = "Red", + FillColor = "Red", + FillStyle = "CrossHatch", + InForeground = True) + # Draw points on the Vertices of the Selected Poly: + self.SelectedPoints = Canvas.AddPointSet(Object.Points, + Diameter = 6, + Color = "Red", + InForeground = True) + self.SelectedPoints.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.SelectPointHit) + Canvas.Draw() + + def DeSelectPoly(self): + Canvas = self.Canvas + if self.SelectedPolyOrig is not None: + self.SelectedPolyOrig.SetPoints(self.SelectedPoly.Points, copy = False) + self.Canvas.Draw(Force = True) + Canvas.RemoveObject(self.SelectedPoly) + Canvas.RemoveObject(self.SelectedPoints) + self.ResetSelections() + + def SelectPointHit(self, PointSet): + PointSet.Index = PointSet.FindClosestPoint(PointSet.HitCoords) + print "point #%i hit"%PointSet.Index + #Index = PointSet.Index + self.PointSelected = True + +class PolyEditor(wx.App): + """ + + A simple example of making editable shapes with FloatCanvas + + """ + + def OnInit(self): + wx.InitAllImageHandlers() + frame = DrawFrame(None, + -1, + "FloatCanvas Demo App", + wx.DefaultPosition, + (700,700), + ) + + self.SetTopWindow(frame) + frame.Show() + + frame.Setup() + return True + +PolyEditor(0).MainLoop()# put in True if you want output to go to it's own window. + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/floatcanvas/ProcessDiagram.py b/samples/floatcanvas/ProcessDiagram.py new file mode 100644 index 00000000..b6b1ca1f --- /dev/null +++ b/samples/floatcanvas/ProcessDiagram.py @@ -0,0 +1,379 @@ +#!/usr/bin/env python +""" + +This is a demo, showing how to work with a "tree" structure + +It demonstrates moving objects around, etc, etc. + +""" + +import wx + +#ver = 'local' +ver = 'installed' + +if ver == 'installed': ## import the installed version + from wx.lib.floatcanvas import NavCanvas, Resources + from wx.lib.floatcanvas import FloatCanvas as FC + from wx.lib.floatcanvas.Utilities import BBox + print "using installed version:", wx.lib.floatcanvas.__version__ +elif ver == 'local': + ## import a local version + import sys + sys.path.append("..") + from floatcanvas import NavCanvas, Resources + from floatcanvas import FloatCanvas as FC + from floatcanvas.Utilities import BBox + +import numpy as N + +## here we create some new mixins: +## fixme: These really belong in floatcanvas package -- but I kind of want to clean it up some first + +class MovingObjectMixin: + """ + Methods required for a Moving object + + """ + def GetOutlinePoints(self): + """ + Returns a set of points with which to draw the outline when moving the + object. + + Points are a NX2 array of (x,y) points in World coordinates. + + + """ + BB = self.BoundingBox + OutlinePoints = N.array( ( (BB[0,0], BB[0,1]), + (BB[0,0], BB[1,1]), + (BB[1,0], BB[1,1]), + (BB[1,0], BB[0,1]), + ) + ) + + return OutlinePoints + +class ConnectorObjectMixin: + """ + Mixin class for DrawObjects that can be connected with lines + + Note that this version only works for Objects that have an "XY" attribute: + that is, one that is derived from XHObjectMixin. + + """ + + def GetConnectPoint(self): + return self.XY + +class MovingBitmap(FC.ScaledBitmap, MovingObjectMixin, ConnectorObjectMixin): + """ + ScaledBitmap Object that can be moved + """ + ## All we need to do is is inherit from: + ## ScaledBitmap, MovingObjectMixin and ConnectorObjectMixin + pass + +class MovingCircle(FC.Circle, MovingObjectMixin, ConnectorObjectMixin): + """ + ScaledBitmap Object that can be moved + """ + ## All we need to do is is inherit from: + ## Circle MovingObjectMixin and ConnectorObjectMixin + pass + + +class MovingGroup(FC.Group, MovingObjectMixin, ConnectorObjectMixin): + + def GetConnectPoint(self): + return self.BoundingBox.Center + +class NodeObject(FC.Group, MovingObjectMixin, ConnectorObjectMixin): + """ + A version of the moving group for nodes -- an ellipse with text on it. + """ + def __init__(self, + Label, + XY, + WH, + BackgroundColor = "Yellow", + TextColor = "Black", + InForeground = False, + IsVisible = True): + XY = N.asarray(XY, N.float).reshape(2,) + WH = N.asarray(WH, N.float).reshape(2,) + Label = FC.ScaledText(Label, + XY, + Size = WH[1] / 2.0, + Color = TextColor, + Position = 'cc', + ) + self.Ellipse = FC.Ellipse( (XY - WH/2.0), + WH, + FillColor = BackgroundColor, + LineStyle = None, + ) + FC.Group.__init__(self, [self.Ellipse, Label], InForeground, IsVisible) + + def GetConnectPoint(self): + return self.BoundingBox.Center + + +class MovingText(FC.ScaledText, MovingObjectMixin, ConnectorObjectMixin): + """ + ScaledBitmap Object that can be moved + """ + ## All we need to do is is inherit from: + ## ScaledBitmap, MovingObjectMixin and ConnectorObjectMixin + pass + +class MovingTextBox(FC.ScaledTextBox, MovingObjectMixin, ConnectorObjectMixin): + """ + ScaledBitmap Object that can be moved + """ + ## All we need to do is is inherit from: + ## ScaledBitmap, MovingObjectMixin and ConnectorObjectMixin + pass + +class ConnectorLine(FC.LineOnlyMixin, FC.DrawObject,): + """ + + A Line that connects two objects -- it uses the objects to get its coordinates + The objects must have a GetConnectPoint() method. + + """ + ##fixme: this should be added to the Main FloatCanvas Objects some day. + def __init__(self, + Object1, + Object2, + LineColor = "Black", + LineStyle = "Solid", + LineWidth = 1, + InForeground = False): + FC.DrawObject.__init__(self, InForeground) + + self.Object1 = Object1 + self.Object2 = Object2 + self.LineColor = LineColor + self.LineStyle = LineStyle + self.LineWidth = LineWidth + + self.CalcBoundingBox() + self.SetPen(LineColor,LineStyle,LineWidth) + + self.HitLineWidth = max(LineWidth,self.MinHitLineWidth) + + def CalcBoundingBox(self): + self.BoundingBox = BBox.fromPoints((self.Object1.GetConnectPoint(), + self.Object2.GetConnectPoint()) ) + if self._Canvas: + self._Canvas.BoundingBoxDirty = True + + + def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None): + Points = N.array( (self.Object1.GetConnectPoint(), + self.Object2.GetConnectPoint()) ) + Points = WorldToPixel(Points) + dc.SetPen(self.Pen) + dc.DrawLines(Points) + if HTdc and self.HitAble: + HTdc.SetPen(self.HitPen) + HTdc.DrawLines(Points) + + +class TriangleShape1(FC.Polygon, MovingObjectMixin): + + def __init__(self, XY, L): + + """ + An equilateral triangle object + XY is the middle of the triangle + L is the length of one side of the Triangle + """ + + XY = N.asarray(XY) + XY.shape = (2,) + + Points = self.CompPoints(XY, L) + + FC.Polygon.__init__(self, Points, + LineColor = "Black", + LineStyle = "Solid", + LineWidth = 2, + FillColor = "Red", + FillStyle = "Solid") + ## Override the default OutlinePoints + def GetOutlinePoints(self): + return self.Points + + def CompPoints(self, XY, L): + c = L/ N.sqrt(3) + + Points = N.array(((0, c), + ( L/2.0, -c/2.0), + (-L/2.0, -c/2.0)), + N.float_) + + Points += XY + return Points + +### Tree Utilities +### And some hard coded data... + +class TreeNode: + dx = 15 + dy = 4 + def __init__(self, name, Children = []): + self.Name = name + #self.parent = None -- Is this needed? + self.Children = Children + self.Point = None # The coords of the node. + + def __str__(self): + return "TreeNode: %s"%self.Name + __repr__ = __str__ + + +## Build Tree: +leaves = [TreeNode(name) for name in ["Assistant VP 1","Assistant VP 2","Assistant VP 3"] ] +VP1 = TreeNode("VP1", Children = leaves) +VP2 = TreeNode("VP2") + +CEO = TreeNode("CEO", [VP1, VP2]) +Father = TreeNode("Father", [TreeNode("Daughter"), TreeNode("Son")]) +elements = TreeNode("Root", [CEO, Father]) + +def LayoutTree(root, x, y, level): + NumNodes = len(root.Children) + root.Point = (x,y) + x += root.dx + y += (root.dy * level * (NumNodes-1) / 2.0) + for node in root.Children: + LayoutTree(node, x, y, level-1) + y -= root.dy * level + +def TraverseTree(root, func): + func(root) + for child in (root.Children): + TraverseTree(child, func) + +class DrawFrame(wx.Frame): + + """ + A simple frame used for the Demo + + """ + + def __init__(self, *args, **kwargs): + wx.Frame.__init__(self, *args, **kwargs) + + self.CreateStatusBar() + # Add the Canvas + Canvas = NavCanvas.NavCanvas(self,-1,(500,500), + ProjectionFun = None, + Debug = 0, + BackgroundColor = "White", + ).Canvas + + self.Canvas = Canvas + + + Canvas.Bind(FC.EVT_MOTION, self.OnMove ) + Canvas.Bind(FC.EVT_LEFT_UP, self.OnLeftUp ) + + self.elements = elements + LayoutTree(self.elements, 0, 0, 3) + self.AddTree(self.elements) + + + self.Show(True) + self.Canvas.ZoomToBB() + + self.MoveObject = None + self.Moving = False + + return None + + def AddTree(self, root): + Nodes = [] + Connectors = [] + EllipseW = 15 + EllipseH = 4 + def CreateObject(node): + if node.Children: + object = NodeObject(node.Name, + node.Point, + (15, 4), + BackgroundColor = "Yellow", + TextColor = "Black", + ) + else: + object = MovingTextBox(node.Name, + node.Point, + 2.0, + BackgroundColor = "White", + Color = "Black", + Position = "cl", + PadSize = 1 + ) + node.DrawObject = object + Nodes.append(object) + def AddConnectors(node): + for child in node.Children: + Connector = ConnectorLine(node.DrawObject, child.DrawObject, LineWidth=3, LineColor="Red") + Connectors.append(Connector) + ## create the Objects + TraverseTree(root, CreateObject) + ## create the Connectors + TraverseTree(root, AddConnectors) + ## Add the conenctos to the Canvas first, so they are undernieth the nodes + self.Canvas.AddObjects(Connectors) + ## now add the nodes + self.Canvas.AddObjects(Nodes) + # Now bind the Nodes -- DrawObjects must be Added to a Canvas before they can be bound. + for node in Nodes: + #pass + node.Bind(FC.EVT_FC_LEFT_DOWN, self.ObjectHit) + + + + def ObjectHit(self, object): + if not self.Moving: + self.Moving = True + self.StartPoint = object.HitCoordsPixel + self.StartObject = self.Canvas.WorldToPixel(object.GetOutlinePoints()) + self.MoveObject = None + self.MovingObject = object + + def OnMove(self, event): + """ + Updates the status bar with the world coordinates + and moves the object it is clicked on + + """ + self.SetStatusText("%.4f, %.4f"%tuple(event.Coords)) + + if self.Moving: + dxy = event.GetPosition() - self.StartPoint + # Draw the Moving Object: + dc = wx.ClientDC(self.Canvas) + dc.SetPen(wx.Pen('WHITE', 2, wx.SHORT_DASH)) + dc.SetBrush(wx.TRANSPARENT_BRUSH) + dc.SetLogicalFunction(wx.XOR) + if self.MoveObject is not None: + dc.DrawPolygon(self.MoveObject) + self.MoveObject = self.StartObject + dxy + dc.DrawPolygon(self.MoveObject) + + def OnLeftUp(self, event): + if self.Moving: + self.Moving = False + if self.MoveObject is not None: + dxy = event.GetPosition() - self.StartPoint + dxy = self.Canvas.ScalePixelToWorld(dxy) + self.MovingObject.Move(dxy) + self.Canvas.Draw(True) + +app = wx.PySimpleApp(0) +DrawFrame(None, -1, "FloatCanvas Tree Demo App", wx.DefaultPosition, (700,700) ) +app.MainLoop() diff --git a/samples/floatcanvas/ScaleDemo.py b/samples/floatcanvas/ScaleDemo.py new file mode 100644 index 00000000..1ce05205 --- /dev/null +++ b/samples/floatcanvas/ScaleDemo.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python + +""" +This demonstrates how to use FloatCanvas with a coordinate system where +X and Y have different scales. In the example, a user had: + +X data in the range: 50e-6 to 2000e-6 +Y data in the range: 0 to 50000 + +-chb + +""" + +import wx + +## import the installed version +from wx.lib.floatcanvas import NavCanvas, FloatCanvas + +## import a local version +#import sys +#sys.path.append("..") +#from floatcanvas import NavCanvas, FloatCanvas + + +import numpy as N + +def YScaleFun(center): + """ + function that returns a scaling vector to scale y data to same range as x data + + """ + # center gets ignored in this case + return N.array((5e7, 1), N.float) + +class DrawFrame(wx.Frame): + + """ + A frame used for the FloatCanvas Demo + + """ + + def __init__(self, *args, **kwargs): + wx.Frame.__init__(self, *args, **kwargs) + + self.CreateStatusBar() + + # Add the Canvas + Canvas = NavCanvas.NavCanvas(self,-1, + size = (500,500), + ProjectionFun = YScaleFun, + Debug = 0, + BackgroundColor = "DARK SLATE BLUE", + ).Canvas + + self.Canvas = Canvas + + Point = N.array((50e-6, 0)) + Size = N.array(( (2000e-6 - 5e-6), 50000)) + Box = Canvas.AddRectangle(Point, + Size, + FillColor = "blue" + ) + + Canvas.AddText("%s"%(Point,), Point, Position="cr") + Canvas.AddPoint(Point, Diameter=3, Color = "red") + + + Point = Point + Size + Canvas.AddText("%s"%(Point,), Point, Position="cl") + Canvas.AddPoint(Point, Diameter=3, Color = "red") + + FloatCanvas.EVT_MOTION(self.Canvas, self.OnMove ) + + + self.Show() + Canvas.ZoomToBB() + + def OnMove(self, event): + """ + Updates the status bar with the world coordinates + """ + self.SetStatusText("%.2g, %.2g"%tuple(event.Coords)) + + +app = wx.App(False) +F = DrawFrame(None, title="FloatCanvas Demo App", size=(700,700) ) +app.MainLoop() + + + + + + + + + + + + + diff --git a/samples/floatcanvas/ScaledBitmap2Demo.py b/samples/floatcanvas/ScaledBitmap2Demo.py new file mode 100755 index 00000000..e7d068f5 --- /dev/null +++ b/samples/floatcanvas/ScaledBitmap2Demo.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python + +""" +This demo shows how to use a ScaledBitmap2 (which is like a scaled bitmap, +but uses memory more efficiently for large images and high zoom levels.) + + +""" + +## Set a path to an Image file here: +ImageFile = "white_tank.jpg" + + +import wx +import random + +## import the installed version +from wx.lib.floatcanvas import NavCanvas, FloatCanvas + +## import a local version +#import sys +#sys.path.append("../") +#from floatcanvas import NavCanvas, FloatCanvas + +class DrawFrame(wx.Frame): + + """ + A frame used for the FloatCanvas Demo + + """ + + def __init__(self, *args, **kwargs): + wx.Frame.__init__(self, *args, **kwargs) + self.CreateStatusBar() + + # Add the Canvas + Canvas = NavCanvas.NavCanvas(self, + ProjectionFun = None, + BackgroundColor = "DARK SLATE BLUE", + ).Canvas + Canvas.MaxScale=20 # sets the maximum zoom level + self.Canvas = Canvas + + FloatCanvas.EVT_MOTION(self.Canvas, self.OnMove ) + + + # create the image: + image = wx.Image(ImageFile) + self.width, self.height = image.GetSize() + img = FloatCanvas.ScaledBitmap2( image, + (0,0), + Height=image.GetHeight(), + Position = 'tl', + ) + Canvas.AddObject(img) + + self.Show() + Canvas.ZoomToBB() + + def OnMove(self, event): + """ + Updates the status bar with the world coordinates + + """ + self.SetStatusText("%i, %i"%tuple(event.Coords)) + + +app = wx.App(False) +F = DrawFrame(None, title="FloatCanvas Demo App", size=(700,700) ) +app.MainLoop() + + + + + + + + + + + + + diff --git a/samples/floatcanvas/SplitterWindow.py b/samples/floatcanvas/SplitterWindow.py new file mode 100644 index 00000000..612125e9 --- /dev/null +++ b/samples/floatcanvas/SplitterWindow.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python + +import wx + +## import the installed version +from wx.lib.floatcanvas import NavCanvas, FloatCanvas + +## import a local version +#import sys +#sys.path.append("../") +#from floatcanvas import NavCanvas, FloatCanvas + +class MyFrame(wx.Frame): + def __init__(self, *args, **kwargs): + wx.Frame.__init__(self, *args, **kwargs) + + #Adding the SplitterWindow + splitter = wx.SplitterWindow(self, style=wx.SP_LIVE_UPDATE|wx.SP_3D, size = (800,400)) + + # add the left Panel + panel1 = wx.Panel(splitter) + panel1.SetBackgroundColour(wx.RED) + + # add the Canvas + panel2 = NavCanvas.NavCanvas(splitter, + ProjectionFun = None, + Debug = 0, + BackgroundColor = "DARK SLATE BLUE", + ) + Canvas = panel2.Canvas + + # put something on the Canvas + Point = (15,10) + Canvas.AddScaledTextBox("A Two Line\nString", + Point, + 2, + Color = "Black", + BackgroundColor = None, + LineColor = "Red", + LineStyle = "Solid", + LineWidth = 1, + Width = None, + PadSize = 5, + Family = wx.ROMAN, + Style = wx.NORMAL, + Weight = wx.NORMAL, + Underlined = False, + Position = 'br', + Alignment = "left", + InForeground = False) + + wx.CallAfter(Canvas.ZoomToBB) + + # set up the Splitter + sash_Position = 300 + splitter.SplitVertically(panel1, panel2, sash_Position) + splitter.SetSashSize(10) + min_Pan_size = 40 + splitter.SetMinimumPaneSize(min_Pan_size) + + self.Fit() + + +class MyApp(wx.App): + def OnInit(self): + frame = MyFrame(None, title='splitter test') + frame.Show(True) + self.SetTopWindow(frame) + return True + +app = MyApp(0) +app.MainLoop() diff --git a/samples/floatcanvas/SubClassNavCanvas.py b/samples/floatcanvas/SubClassNavCanvas.py new file mode 100755 index 00000000..346ae4a2 --- /dev/null +++ b/samples/floatcanvas/SubClassNavCanvas.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python + +""" +A simple example of sub-classing the Navcanvas + +-- an alternative to simply putting a NavCanvas on your yoru oen panle or whatever +""" + +import wx + +## import the installed version +from wx.lib.floatcanvas import NavCanvas, FloatCanvas + +## import a local version +#import sys +#sys.path.append("../") +#from floatcanvas import NavCanvas, FloatCanvas + +class DrawFrame(wx.Frame): + + """ + A frame used for the Demo + """ + + def __init__(self, *args, **kwargs): + wx.Frame.__init__(self, *args, **kwargs) + self.CreateStatusBar() + + panel = NavPanel(self) + + self.Show() + +class NavPanel(NavCanvas.NavCanvas): + """ + a subclass of NavCAnvas -- with some specific drawing code + """ + def __init__(self, parent): + NavCanvas.NavCanvas.__init__(self, + parent, + ProjectionFun = None, + BackgroundColor = "DARK SLATE BLUE", + ) + + self.parent_frame = parent + FloatCanvas.EVT_MOTION(self.Canvas, self.OnMove ) + + # create the image: + self.Canvas.AddPolygon( ( (2,3), + (5,6), + (7,1), ), + FillColor = "red", + ) + + + wx.CallAfter(self.Canvas.ZoomToBB) # so it will get called after everything is created and sized + + def OnMove(self, event): + """ + Updates the status bar with the world coordinates + + """ + self.parent_frame.SetStatusText("%f, %f"%tuple(event.Coords)) + pass + +app = wx.App(False) +F = DrawFrame(None, title="FloatCanvas Demo App", size=(700,700) ) +app.MainLoop() diff --git a/samples/floatcanvas/TestSpline.py b/samples/floatcanvas/TestSpline.py new file mode 100755 index 00000000..6a06c825 --- /dev/null +++ b/samples/floatcanvas/TestSpline.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python + + +""" +This is a very small app using the FloatCanvas + +It tests the Spline object, including how you can put points together to +create an object with curves and square corners. + + +""" +import wx + +#### import local version: +#import sys +#sys.path.append("../") +#from floatcanvas import NavCanvas +#from floatcanvas import FloatCanvas as FC + +from wx.lib.floatcanvas import FloatCanvas as FC +from wx.lib.floatcanvas import NavCanvas + +class Spline(FC.Line): + def __init__(self, *args, **kwargs): + FC.Line.__init__(self, *args, **kwargs) + + def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None): + Points = WorldToPixel(self.Points) + dc.SetPen(self.Pen) + dc.DrawSpline(Points) + if HTdc and self.HitAble: + HTdc.SetPen(self.HitPen) + HTdc.DrawSpline(Points) + +class DrawFrame(wx.Frame): + + """ + A frame used for the FloatCanvas + + """ + def __init__(self, *args, **kwargs): + wx.Frame.__init__(self, *args, **kwargs) + + ## Set up the MenuBar + MenuBar = wx.MenuBar() + + file_menu = wx.Menu() + item = file_menu.Append(-1, "&Close","Close this frame") + self.Bind(wx.EVT_MENU, self.OnQuit, item) + MenuBar.Append(file_menu, "&File") + + help_menu = wx.Menu() + item = help_menu.Append(-1, "&About", + "More information About this program") + self.Bind(wx.EVT_MENU, self.OnAbout, item) + MenuBar.Append(help_menu, "&Help") + + self.SetMenuBar(MenuBar) + self.CreateStatusBar() + + # Add the Canvas + self.Canvas = NavCanvas.NavCanvas(self, + BackgroundColor = "White", + ).Canvas + + self.Canvas.Bind(FC.EVT_MOTION, self.OnMove) + + wx.EVT_CLOSE(self, self.OnCloseWindow) + + self.DrawTest() + self.Show() + self.Canvas.ZoomToBB() + + def OnAbout(self, event): + print "OnAbout called" + + dlg = wx.MessageDialog(self, "This is a small program to demonstrate\n" + "the use of the FloatCanvas\n", + "About Me", wx.OK | wx.ICON_INFORMATION) + dlg.ShowModal() + dlg.Destroy() + + def OnMove(self, event): + """ + Updates the status bar with the world coordinates + """ + self.SetStatusText("%.2f, %.2f"%tuple(event.Coords)) + + def OnQuit(self,event): + self.Close(True) + + def OnCloseWindow(self, event): + self.Destroy() + + def DrawTest(self,event=None): + wx.GetApp().Yield() + + Canvas = self.Canvas + + Points = [(0, 0), + (200,0), + (200,0), + (200,0), + (200,15), + (185,15), + (119,15), + (104,15), + (104,30), + (104,265), + (104,280), + (119,280), + (185,280), + (200,280), + (200,295), + (200,295), + (200,295), + (0, 295), + (0, 295), + (0, 295), + (0, 280), + (15, 280), + (81, 280), + (96, 280), + (96, 265), + (96, 30), + (96, 15), + (81, 15), + (15, 15), + (0, 15), + (0, 0), + ] + + Canvas.ClearAll() + + MyLine = FC.Spline(Points, + LineWidth = 3, + LineColor = "Blue") + + Canvas.AddObject(MyLine) + Canvas.AddPointSet(Points, + Color = "Red", + Diameter = 4, + ) + + ## A regular old spline: + Points = [(-30, 260), + (-10, 130), + (70, 185), + (160,60), + ] + + Canvas.AddSpline(Points, + LineWidth = 5, + LineColor = "Purple") + +class DemoApp(wx.App): + + def __init__(self, *args, **kwargs): + wx.App.__init__(self, *args, **kwargs) + + def OnInit(self): + frame = DrawFrame(None, title="FloatCanvas Spline Demo", size = (700,700)) + + self.SetTopWindow(frame) + return True + +app = DemoApp(False)# put in True if you want output to go to it's own window. +app.MainLoop() + + + + + + + + + + + + + + + + + diff --git a/samples/floatcanvas/TextBox.py b/samples/floatcanvas/TextBox.py new file mode 100755 index 00000000..429d1d34 --- /dev/null +++ b/samples/floatcanvas/TextBox.py @@ -0,0 +1,302 @@ +#!/usr/bin/env python +""" + +A test and demo of the features of the ScaledTextBox + +""" + +import wx + +## import the installed version +from wx.lib.floatcanvas import NavCanvas, FloatCanvas + +## import the local version +#import sys +#sys.path.append("..") +#from floatcanvas import NavCanvas, FloatCanvas + +import numpy as N + +class DrawFrame(wx.Frame): + + """ + A frame used for the FloatCanvas Demo + + """ + + def __init__(self,parent, id,title,position,size): + wx.Frame.__init__(self,parent, id,title,position, size) + + # Add the Canvas + self.CreateStatusBar() + Canvas = NavCanvas.NavCanvas(self,-1,(500,500), + ProjectionFun = None, + Debug = 0, + BackgroundColor = "DARK SLATE BLUE", + ).Canvas + + self.Canvas = Canvas + FloatCanvas.EVT_MOTION(self.Canvas, self.OnMove ) + + Point = (45,40) + Box = Canvas.AddScaledTextBox("A Two Line\nString", + Point, + 2, + Color = "Black", + BackgroundColor = None, + LineColor = "Red", + LineStyle = "Solid", + LineWidth = 1, + Width = None, + PadSize = 5, + Family = wx.ROMAN, + Style = wx.NORMAL, + Weight = wx.NORMAL, + Underlined = False, + Position = 'br', + Alignment = "left", + InForeground = False) + + # All defaults + Box = Canvas.AddScaledTextBox("A Two Line\nString", + Point, + 2) + + Box = Canvas.AddScaledTextBox("A Two Line\nString", + Point, + 2, + BackgroundColor = "Yellow", + LineColor = "Red", + LineStyle = "Solid", + PadSize = 5, + Family = wx.TELETYPE, + Position = 'bl') + + Box = Canvas.AddScaledTextBox("A Two Line\nString", + Point, + 2, + BackgroundColor = "Yellow", + LineColor = "Red", + LineStyle = "Solid", + PadSize = 5, + Family = wx.TELETYPE, + Position = 'tr') + + + + + Box.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.binding2) + + Canvas.AddPoint(Point, Diameter = 4) + + Point = (45,15) + Box = Canvas.AddScaledTextBox("A Two Line\nString", + Point, + 2, + Color = "Black", + BackgroundColor = 'Red', + LineColor = "Blue", + LineStyle = "LongDash", + LineWidth = 2, + Width = None, + PadSize = 5, + Family = wx.TELETYPE, + Style = wx.NORMAL, + Weight = wx.NORMAL, + Underlined = False, + Position = 'cr', + Alignment = "left", + InForeground = False) + + Box = Canvas.AddScaledTextBox("A Two Line\nString", + Point, + 1.5, + Color = "Black", + BackgroundColor = 'Red', + LineColor = "Blue", + LineStyle = "LongDash", + LineWidth = 2, + Width = None, + PadSize = 5, + Family = wx.TELETYPE, + Style = wx.NORMAL, + Weight = wx.NORMAL, + Underlined = False, + Position = 'cl', + Alignment = "left", + InForeground = False) + + Canvas.AddPoint(Point, Diameter = 4) + + Point = (45,-10) + Box = Canvas.AddScaledTextBox("A Two Line\nString", + Point, + 2, + Color = "Black", + BackgroundColor = 'Red', + LineColor = "Blue", + LineStyle = "LongDash", + LineWidth = 2, + Width = None, + PadSize = 3, + Family = wx.TELETYPE, + Style = wx.NORMAL, + Weight = wx.NORMAL, + Underlined = False, + Position = 'tc', + Alignment = "left", + InForeground = False) + + Box = Canvas.AddScaledTextBox("A three\nLine\nString", + Point, + 1.5, + Color = "Black", + BackgroundColor = 'Red', + LineColor = "Blue", + LineStyle = "LongDash", + LineWidth = 2, + Width = None, + PadSize = 0.5, + Family = wx.TELETYPE, + Style = wx.NORMAL, + Weight = wx.NORMAL, + Underlined = False, + Position = 'bc', + Alignment = "left", + InForeground = False) + + + Canvas.AddPoint(Point, Diameter = 4) + + Box = Canvas.AddScaledTextBox("Some Auto Wrapped Text. There is enough to do.", + (80,40), + 2, + BackgroundColor = 'White', + LineWidth = 2, + Width = 20, + PadSize = 0.5, + Family = wx.TELETYPE, + ) + + Box = Canvas.AddScaledTextBox("Some more auto wrapped text. Wrapped to a different width and right aligned.\n\nThis is another paragraph.", + (80,20), + 2, + BackgroundColor = 'White', + LineWidth = 2, + Width = 40, + PadSize = 0.5, + Family = wx.ROMAN, + Alignment = "right" + ) + Point = N.array((100, -20), N.float_) + Box = Canvas.AddScaledTextBox("Here is even more auto wrapped text. This time the line spacing is set to 0.8. \n\nThe Padding is set to 0.", + Point, + Size = 3, + BackgroundColor = 'White', + LineWidth = 1, + Width = 40, + PadSize = 0.0, + Family = wx.ROMAN, + Position = "cc", + LineSpacing = 0.8 + ) + Canvas.AddPoint(Point, "Red", 2) + + Point = N.array((0, -40), N.float_) +# Point = N.array((0, 0), N.float_) + for Position in ["tl", "bl", "tr", "br"]: +# for Position in ["br"]: + Box = Canvas.AddScaledTextBox("Here is a\nfour liner\nanother line\nPosition=%s"%Position, + Point, + Size = 4, + Color = "Red", + BackgroundColor = None,#'LightBlue', + LineWidth = 1, + LineColor = "White", + Width = None, + PadSize = 2, + Family = wx.ROMAN, + Position = Position, + LineSpacing = 0.8 + ) + Canvas.AddPoint(Point, "Red", 4) + + Point = N.array((-20, 60), N.float_) + Box = Canvas.AddScaledTextBox("Here is some\ncentered\ntext", + Point, + Size = 4, + Color = "Red", + BackgroundColor = 'LightBlue', + LineWidth = 1, + LineColor = "White", + Width = None, + PadSize = 2, + Family = wx.ROMAN, + Position = "tl", + Alignment = "center", + LineSpacing = 0.8 + ) + + Point = N.array((-20, 20), N.float_) + Box = Canvas.AddScaledTextBox("Here is some\nright aligned\ntext", + Point, + Size = 4, + Color = "Red", + BackgroundColor = 'LightBlue', + LineColor = None, + Width = None, + PadSize = 2, + Family = wx.ROMAN, + Position = "tl", + Alignment = "right", + LineSpacing = 0.8 + ) + + Point = N.array((100, -60), N.float_) + Box = Canvas.AddScaledTextBox("Here is some auto wrapped text. This time it is centered, rather than right aligned.\n\nThe Padding is set to 2.", + Point, + Size = 3, + BackgroundColor = 'White', + LineWidth = 1, + Width = 40, + PadSize = 2.0, + Family = wx.ROMAN, + Position = "cc", + LineSpacing = 0.8, + Alignment = 'center', + ) + + + self.Show(True) + self.Canvas.ZoomToBB() + + return None + def OnMove(self, event): + """ + Updates the status bar with the world coordinates + """ + self.SetStatusText("%.2f, %.2f"%tuple(event.Coords)) + + + def binding(self, event): + print "I'm the Rectangle" + + def binding2(self, event): + print "I'm the TextBox" + +app = wx.PySimpleApp() +DrawFrame(None, -1, "FloatCanvas Demo App", wx.DefaultPosition, (700,700) ) +app.MainLoop() + + + + + + + + + + + + + diff --git a/samples/floatcanvas/TextBox2.py b/samples/floatcanvas/TextBox2.py new file mode 100755 index 00000000..bbac53a2 --- /dev/null +++ b/samples/floatcanvas/TextBox2.py @@ -0,0 +1,228 @@ +#!/usr/bin/env python + + +""" +A test and demo of the ScaledTextbox. + +It also shows how one can use the Mouse to interact and change objects on a Canvas. + +this really needs to be re-done with GUI-Modes. +""" + + +import wx + + +## import the installed version +from wx.lib.floatcanvas import NavCanvas, FloatCanvas + +## import a local version +#import sys +#sys.path.append("../") +#from floatcanvas import NavCanvas, FloatCanvas, Resources + + +import numpy as N + +LongString = ( +"""This is a long string. It is a bunch of text. I am using it to test how the nifty wrapping text box works when you want to re-size. + +This is another paragraph. I am trying to make it long enough to wrap a reasonable amount. Let's see how it works. + + + This is a way to start a paragraph with indenting +""" +) + +##LongString = ( +##""" This is a not so long string +##Another line""") + +class DrawFrame(wx.Frame): + + """ + A frame used for the FloatCanvas Demo + + """ + + def __init__(self,parent, id,title,position,size): + wx.Frame.__init__(self,parent, id,title,position, size) + + self.CreateStatusBar() + # Add the Canvas + Canvas = NavCanvas.NavCanvas(self,-1,(500,500), + ProjectionFun = None, + Debug = 0, + BackgroundColor = "DARK SLATE BLUE", + ).Canvas + + self.Canvas = Canvas + + FloatCanvas.EVT_MOTION(self.Canvas, self.OnMove ) + FloatCanvas.EVT_LEFT_UP(self.Canvas, self.OnLeftUp ) + FloatCanvas.EVT_LEFT_DOWN(self.Canvas, self.OnLeftDown) + + Point = N.array((0,0), N.float) + + + + Canvas.AddCircle(Point, + + Diameter=40, + + FillColor="Red", + + LineStyle=None, + + ) + + + Width = 300 + self.Box = Canvas.AddScaledTextBox(LongString, + Point, + 10, + Color = "Black", + BackgroundColor = 'White', + LineStyle = "Solid", + LineWidth = 2, + Width = Width, + PadSize = 10.0, + Family = wx.ROMAN, + #Family = wx.TELETYPE, + Style = wx.NORMAL, + Weight = wx.NORMAL, + Underlined = False, + Position = 'tl', + LineSpacing = 0.8, + Alignment = "left", + #Alignment = "center", + #Alignment = "right", + InForeground = False) + + + self.Handle1 = Canvas.AddBitmap(Resources.getMoveCursorBitmap(), Point, Position='cc') + self.Handle2a = Canvas.AddBitmap(Resources.getMoveRLCursorBitmap(), Point, Position='cc') + self.Handle2b = Canvas.AddBitmap(Resources.getMoveRLCursorBitmap(), Point, Position='cc') + + self.SetHandles() + + self.Handle1.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.Handle1Hit) + self.Handle2a.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.Handle2Hit) + self.Handle2b.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.Handle2Hit) + + + self.Show(True) + self.Canvas.ZoomToBB() + + self.Resizing = False + self.ResizeRect = None + self.Moving = False + + return None + + def Handle1Hit(self, object): + if not self.Moving: + self.Moving = True + self.StartPoint = object.HitCoordsPixel + + def Handle2Hit(self,event=None): + if not self.Resizing: + self.Resizing = True + + def OnMove(self, event): + """ + Updates the status bar with the world coordinates + + And moves a point if there is one + + """ + self.SetStatusText("%.4f, %.4f"%tuple(event.Coords)) + + if self.Resizing: + ((xy),(wh)) = self.Box.GetBoxRect() + (xp, yp) = self.Canvas.WorldToPixel(xy) + (wp, hp) = self.Canvas.ScaleWorldToPixel(wh) + hp = -hp + Corner = event.GetPosition() + if self.Box.Position[1] in 'lc': + wp = max(20, Corner[0]-xp) # don't allow the box to get narrower than 20 pixels + elif self.Box.Position[1] in 'r': + DeltaX = Corner[0]-xp + xp += DeltaX + wp -= DeltaX + # draw the RB box + dc = wx.ClientDC(self.Canvas) + dc.SetPen(wx.Pen('WHITE', 2, wx.SHORT_DASH)) + dc.SetBrush(wx.TRANSPARENT_BRUSH) + dc.SetLogicalFunction(wx.XOR) + if self.ResizeRect: + dc.DrawRectangle(*self.ResizeRect) + self.ResizeRect = (xp,yp,wp,hp) + dc.DrawRectangle(*self.ResizeRect) + elif self.Moving: + dxy = event.GetPosition() - self.StartPoint + ((xy),(wh)) = self.Box.GetBoxRect() + xp, yp = self.Canvas.WorldToPixel(xy) + dxy + (wp, hp) = self.Canvas.ScaleWorldToPixel(wh) + hp = -hp + # draw the RB box + dc = wx.ClientDC(self.Canvas) + dc.SetPen(wx.Pen('WHITE', 2, wx.SHORT_DASH)) + dc.SetBrush(wx.TRANSPARENT_BRUSH) + dc.SetLogicalFunction(wx.XOR) + if self.ResizeRect: + dc.DrawRectangle(*self.ResizeRect) + self.ResizeRect = (xp,yp,wp,hp) + dc.DrawRectangle(*self.ResizeRect) + + def OnLeftDown(self, event): + pass + + def OnLeftUp(self, event): + if self.Resizing: + self.Resizing = False + if self.ResizeRect: + Point = self.Canvas.PixelToWorld(self.ResizeRect[:2]) + W, H = self.Canvas.ScalePixelToWorld(self.ResizeRect[2:4]) + self.ResizeRect = None + self.Box.ReWrap(W) + self.SetHandles() + elif self.Moving: + self.Moving = False + if self.ResizeRect: + dxy = event.GetPosition() - self.StartPoint + dxy = self.Canvas.ScalePixelToWorld(dxy) + self.Box.Move(dxy) + self.ResizeRect = None + # self.Box.SetPoint(Point1) + self.SetHandles() + + self.Canvas.Draw(True) + + def SetHandles(self): + ((x,y),(w,h)) = self.Box.GetBoxRect() + if self.Box.Position[1] in "lc": + x += w + y -= h/3 + self.Handle2a.SetPoint((x,y)) + y -= h/3 + self.Handle2b.SetPoint((x,y)) + self.Handle1.SetPoint(self.Box.XY) + + +app = wx.PySimpleApp() +DrawFrame(None, -1, "FloatCanvas TextBox Test App", wx.DefaultPosition, (700,700) ) +app.MainLoop() + + + + + + + + + + + + + diff --git a/samples/floatcanvas/Tiny.bna b/samples/floatcanvas/Tiny.bna new file mode 100644 index 00000000..72e30d3d --- /dev/null +++ b/samples/floatcanvas/Tiny.bna @@ -0,0 +1,38 @@ +"Another Name","Another Type", 19 +-81.531753540039,31.134635925293 +-81.531150817871,31.134529113769 +-81.530662536621,31.134353637695 +-81.530502319336,31.134126663208 +-81.530685424805,31.133970260620 +-81.531112670898,31.134040832519 +-81.532104492188,31.134008407593 +-81.532485961914,31.134220123291 +-81.533134460449,31.134204864502 +-81.534004211426,31.134277343750 +-81.534667968750,31.134349822998 +-81.534912109375,31.134525299072 +-81.534667968750,31.134855270386 +-81.534248352051,31.134975433350 +-81.533943176270,31.135166168213 +-81.533760070801,31.135200500488 +-81.532928466797,31.135110855102 +-81.532447814941,31.134794235229 +-81.532341003418,31.134586334229 +"A third 'name'","6", 7 +-81.522369384766,31.122062683106 +-81.522109985352,31.121908187866 +-81.522010803223,31.121685028076 +-81.522254943848,31.121658325195 +-81.522483825684,31.121797561646 +-81.522514343262,31.122062683106 +-81.522369384766,31.122062683106 +"8223","1", 9 +-81.523277282715,31.122261047363 +-81.522987365723,31.121982574463 +-81.523200988770,31.121547698975 +-81.523361206055,31.121408462524 +-81.523818969727,31.121549606323 +-81.524078369141,31.121662139893 +-81.524009704590,31.121944427490 +-81.523925781250,31.122068405151 +-81.523277282715,31.122261047363 diff --git a/samples/floatcanvas/Tree.py b/samples/floatcanvas/Tree.py new file mode 100755 index 00000000..f7d35605 --- /dev/null +++ b/samples/floatcanvas/Tree.py @@ -0,0 +1,371 @@ +#!/usr/bin/env python +""" + +This is a demo, showing how to work with a "tree" structure + +It demonstrates moving objects around, etc, etc. + +""" + +import wx + +#ver = 'local' +2ver = 'installed' + +if ver == 'installed': ## import the installed version + from wx.lib.floatcanvas import NavCanvas, Resources + from wx.lib.floatcanvas import FloatCanvas as FC + from wx.lib.floatcanvas.Utilities import BBox + print "using installed version:", wx.lib.floatcanvas.__version__ +elif ver == 'local': + ## import a local version + import sys + sys.path.append("..") + from floatcanvas import NavCanvas, Resources + from floatcanvas import FloatCanvas as FC + from floatcanvas.Utilities import BBox + +import numpy as N + +## here we create some new mixins: +## fixme: These really belong in floatcanvas package -- but I kind of want to clean it up some first + +class MovingObjectMixin: + """ + Methods required for a Moving object + + """ + def GetOutlinePoints(self): + """ + Returns a set of points with which to draw the outline when moving the + object. + + Points are a NX2 array of (x,y) points in World coordinates. + + + """ + BB = self.BoundingBox + OutlinePoints = N.array( ( (BB[0,0], BB[0,1]), + (BB[0,0], BB[1,1]), + (BB[1,0], BB[1,1]), + (BB[1,0], BB[0,1]), + ) + ) + + return OutlinePoints + +class ConnectorObjectMixin: + """ + Mixin class for DrawObjects that can be connected with lines + + Note that this version only works for Objects that have an "XY" attribute: + that is, one that is derived from XHObjectMixin. + + """ + + def GetConnectPoint(self): + return self.XY + +class MovingBitmap(FC.ScaledBitmap, MovingObjectMixin, ConnectorObjectMixin): + """ + ScaledBitmap Object that can be moved + """ + ## All we need to do is is inherit from: + ## ScaledBitmap, MovingObjectMixin and ConnectorObjectMixin + pass + +class MovingCircle(FC.Circle, MovingObjectMixin, ConnectorObjectMixin): + """ + ScaledBitmap Object that can be moved + """ + ## All we need to do is is inherit from: + ## Circle MovingObjectMixin and ConnectorObjectMixin + pass + + +class MovingGroup(FC.Group, MovingObjectMixin, ConnectorObjectMixin): + + def GetConnectPoint(self): + return self.BoundingBox.Center + +class NodeObject(FC.Group, MovingObjectMixin, ConnectorObjectMixin): + """ + A version of the moving group for nodes -- an ellipse with text on it. + """ + def __init__(self, + Label, + XY, + WH, + BackgroundColor = "Yellow", + TextColor = "Black", + InForeground = False, + IsVisible = True): + XY = N.asarray(XY, N.float).reshape(2,) + WH = N.asarray(WH, N.float).reshape(2,) + Label = FC.ScaledText(Label, + XY, + Size = WH[1] / 2.0, + Color = TextColor, + Position = 'cc', + ) + self.Ellipse = FC.Ellipse( (XY - WH/2.0), + WH, + FillColor = BackgroundColor, + LineStyle = None, + ) + FC.Group.__init__(self, [self.Ellipse, Label], InForeground, IsVisible) + + def GetConnectPoint(self): + return self.BoundingBox.Center + + +class MovingText(FC.ScaledText, MovingObjectMixin, ConnectorObjectMixin): + """ + ScaledBitmap Object that can be moved + """ + ## All we need to do is is inherit from: + ## ScaledBitmap, MovingObjectMixin and ConnectorObjectMixin + pass + +class ConnectorLine(FC.LineOnlyMixin, FC.DrawObject,): + """ + + A Line that connects two objects -- it uses the objects to get its coordinates + The objects must have a GetConnectPoint() method. + + """ + ##fixme: this should be added to the Main FloatCanvas Objects some day. + def __init__(self, + Object1, + Object2, + LineColor = "Black", + LineStyle = "Solid", + LineWidth = 1, + InForeground = False): + FC.DrawObject.__init__(self, InForeground) + + self.Object1 = Object1 + self.Object2 = Object2 + self.LineColor = LineColor + self.LineStyle = LineStyle + self.LineWidth = LineWidth + + self.CalcBoundingBox() + self.SetPen(LineColor,LineStyle,LineWidth) + + self.HitLineWidth = max(LineWidth,self.MinHitLineWidth) + + def CalcBoundingBox(self): + self.BoundingBox = BBox.fromPoints((self.Object1.GetConnectPoint(), + self.Object2.GetConnectPoint()) ) + if self._Canvas: + self._Canvas.BoundingBoxDirty = True + + + def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None): + Points = N.array( (self.Object1.GetConnectPoint(), + self.Object2.GetConnectPoint()) ) + Points = WorldToPixel(Points) + dc.SetPen(self.Pen) + dc.DrawLines(Points) + if HTdc and self.HitAble: + HTdc.SetPen(self.HitPen) + HTdc.DrawLines(Points) + + +class TriangleShape1(FC.Polygon, MovingObjectMixin): + + def __init__(self, XY, L): + + """ + An equilateral triangle object + XY is the middle of the triangle + L is the length of one side of the Triangle + """ + + XY = N.asarray(XY) + XY.shape = (2,) + + Points = self.CompPoints(XY, L) + + FC.Polygon.__init__(self, Points, + LineColor = "Black", + LineStyle = "Solid", + LineWidth = 2, + FillColor = "Red", + FillStyle = "Solid") + ## Override the default OutlinePoints + def GetOutlinePoints(self): + return self.Points + + def CompPoints(self, XY, L): + c = L/ N.sqrt(3) + + Points = N.array(((0, c), + ( L/2.0, -c/2.0), + (-L/2.0, -c/2.0)), + N.float_) + + Points += XY + return Points + +### Tree Utilities +### And some hard coded data... + +class TreeNode: + dx = 15 + dy = 4 + def __init__(self, name, Children = []): + self.Name = name + #self.parent = None -- Is this needed? + self.Children = Children + self.Point = None # The coords of the node. + + def __str__(self): + return "TreeNode: %s"%self.Name + __repr__ = __str__ + + +## Build Tree: +leaves = [TreeNode(name) for name in ["Assistant VP 1","Assistant VP 2","Assistant VP 3"] ] +VP1 = TreeNode("VP1", Children = leaves) +VP2 = TreeNode("VP2") + +CEO = TreeNode("CEO", [VP1, VP2]) +Father = TreeNode("Father", [TreeNode("Daughter"), TreeNode("Son")]) +elements = TreeNode("Root", [CEO, Father]) + +def LayoutTree(root, x, y, level): + NumNodes = len(root.Children) + root.Point = (x,y) + x += root.dx + y += (root.dy * level * (NumNodes-1) / 2.0) + for node in root.Children: + LayoutTree(node, x, y, level-1) + y -= root.dy * level + +def TraverseTree(root, func): + func(root) + for child in (root.Children): + TraverseTree(child, func) + +class DrawFrame(wx.Frame): + + """ + A simple frame used for the Demo + + """ + + def __init__(self, *args, **kwargs): + wx.Frame.__init__(self, *args, **kwargs) + + self.CreateStatusBar() + # Add the Canvas + Canvas = NavCanvas.NavCanvas(self,-1,(500,500), + ProjectionFun = None, + Debug = 0, + BackgroundColor = "White", + ).Canvas + + self.Canvas = Canvas + + + Canvas.Bind(FC.EVT_MOTION, self.OnMove ) + Canvas.Bind(FC.EVT_LEFT_UP, self.OnLeftUp ) + + self.elements = elements + LayoutTree(self.elements, 0, 0, 3) + self.AddTree(self.elements) + + + self.Show(True) + self.Canvas.ZoomToBB() + + self.MoveObject = None + self.Moving = False + + return None + + def AddTree(self, root): + Nodes = [] + Connectors = [] + EllipseW = 15 + EllipseH = 4 + def CreateObject(node): + if node.Children: + object = NodeObject(node.Name, + node.Point, + (15, 4), + BackgroundColor = "Yellow", + TextColor = "Black", + ) + else: + object = MovingText(node.Name, + node.Point, + 2.0, + BackgroundColor = "Yellow", + Color = "Red", + Position = "cl", + ) + node.DrawObject = object + Nodes.append(object) + def AddConnectors(node): + for child in node.Children: + Connector = ConnectorLine(node.DrawObject, child.DrawObject, LineWidth=3, LineColor="Red") + Connectors.append(Connector) + ## create the Objects + TraverseTree(root, CreateObject) + ## create the Connectors + TraverseTree(root, AddConnectors) + ## Add the conenctos to the Canvas first, so they are undernieth the nodes + self.Canvas.AddObjects(Connectors) + ## now add the nodes + self.Canvas.AddObjects(Nodes) + # Now bind the Nodes -- DrawObjects must be Added to a Canvas before they can be bound. + for node in Nodes: + #pass + node.Bind(FC.EVT_FC_LEFT_DOWN, self.ObjectHit) + + + + def ObjectHit(self, object): + if not self.Moving: + self.Moving = True + self.StartPoint = object.HitCoordsPixel + self.StartObject = self.Canvas.WorldToPixel(object.GetOutlinePoints()) + self.MoveObject = None + self.MovingObject = object + + def OnMove(self, event): + """ + Updates the status bar with the world coordinates + and moves the object it is clicked on + + """ + self.SetStatusText("%.4f, %.4f"%tuple(event.Coords)) + + if self.Moving: + dxy = event.GetPosition() - self.StartPoint + # Draw the Moving Object: + dc = wx.ClientDC(self.Canvas) + dc.SetPen(wx.Pen('WHITE', 2, wx.SHORT_DASH)) + dc.SetBrush(wx.TRANSPARENT_BRUSH) + dc.SetLogicalFunction(wx.XOR) + if self.MoveObject is not None: + dc.DrawPolygon(self.MoveObject) + self.MoveObject = self.StartObject + dxy + dc.DrawPolygon(self.MoveObject) + + def OnLeftUp(self, event): + if self.Moving: + self.Moving = False + if self.MoveObject is not None: + dxy = event.GetPosition() - self.StartPoint + dxy = self.Canvas.ScalePixelToWorld(dxy) + self.MovingObject.Move(dxy) + self.MoveTri = None + self.Canvas.Draw(True) + +app = wx.PySimpleApp(0) +DrawFrame(None, -1, "FloatCanvas Tree Demo App", wx.DefaultPosition, (700,700) ) +app.MainLoop() diff --git a/samples/floatcanvas/VectPlot.py b/samples/floatcanvas/VectPlot.py new file mode 100755 index 00000000..25cab46f --- /dev/null +++ b/samples/floatcanvas/VectPlot.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python +""" + +A small test app that uses FloatCanvas to draw a vector plot. + +""" + +import wx +import numpy as N +import random + +## import the installed version +from wx.lib.floatcanvas import NavCanvas, FloatCanvas + +## import a local version +#import sys +#sys.path.append("../") +#from floatcanvas import NavCanvas, FloatCanvas + + +class DrawFrame(wx.Frame): + def __init__(self,parent, id,title,position,size): + wx.Frame.__init__(self,parent, id,title,position, size) + + ## Set up the MenuBar + + MenuBar = wx.MenuBar() + + file_menu = wx.Menu() + item = file_menu.Append(wx.ID_ANY, "E&xit","Terminate the program") + self.Bind(wx.EVT_MENU, self.OnQuit, item) + MenuBar.Append(file_menu, "&File") + + draw_menu = wx.Menu() + item = draw_menu.Append(wx.ID_ANY, "&Plot","Re-do Plot") + self.Bind(wx.EVT_MENU, self.Plot, item) + MenuBar.Append(draw_menu, "&Plot") + + + help_menu = wx.Menu() + item = help_menu.Append(wx.ID_ANY, "&About", + "More information About this program") + self.Bind(wx.EVT_MENU, self.OnAbout, item) + MenuBar.Append(help_menu, "&Help") + + self.SetMenuBar(MenuBar) + + + self.CreateStatusBar() + self.SetStatusText("") + + wx.EVT_CLOSE(self, self.OnCloseWindow) + + # Add the Canvas + self.Canvas = NavCanvas.NavCanvas(self ,wx.ID_ANY ,(500,300), + ProjectionFun = None, + Debug = 0, + BackgroundColor = "WHITE" + ).Canvas + + self.Canvas.NumBetweenBlits = 1000 + + + self.Show(True) + + self.Plot() + return None + + + def OnAbout(self, event): + dlg = wx.MessageDialog(self, "This is a small program to demonstrate\n" + "the use of the FloatCanvas\n" + "for vector plotting", + "About Me", wx.OK | wx.ICON_INFORMATION) + dlg.ShowModal() + dlg.Destroy() + + def ZoomToFit(self,event): + self.Canvas.ZoomToBB() + + def OnQuit(self,event): + self.Close(True) + + def OnCloseWindow(self, event): + self.Destroy() + + + def DrawAxis(self): + Canvas = self.Canvas + + # Draw the Axis + + # Note: the AddRectangle Parameters all have sensible + # defaults. I've put them all here explicitly, so you can see + # what the options are. + + self.Canvas.AddRectangle((0, -1.1), + (2*N.pi, 2.2), + LineColor = "Black", + LineStyle = "Solid", + LineWidth = 1, + FillColor = None, + FillStyle = "Solid", + InForeground = 0) + for tic in N.arange(7): + self.Canvas.AddText("%1.1f"%tic, + (tic, -1.1), + Position = 'tc') + + for tic in N.arange(-1,1.1,0.5): + self.Canvas.AddText("%1.1f"%tic, + (0,tic), + Position = 'cr') + + # Add a phantom rectangle to get the bounding box right + # (the bounding box doesn't get unscaled text right) + self.Canvas.AddRectangle((-0.7, -1.5), + (7, 3), + LineColor = None) + + #Canvas.ZoomToBB() + #Canvas.Draw() + + def Plot(self, event = None): + x = N.arange(0, 2*N.pi, 0.1) + x.shape = (-1,1) + y = N.sin(x) + data = N.concatenate((x, y),1) + + Canvas = self.Canvas + self.Canvas.ClearAll() + self.DrawAxis() + for p in data: + Canvas.AddPoint(p, + Diameter = 4, + Color = "Red", + InForeground = 1) + theta = random.uniform(0, 360) + Canvas.AddArrow(p, + Length = 20, + Direction = p[1]*360, + LineColor = "Red", + ) + self.Canvas.ZoomToBB() + self.Canvas.Draw() + self.Canvas.SaveAsImage("junk.png") + + +class DemoApp(wx.App): + """ + How the demo works: + + Either under the Draw menu, or on the toolbar, you can push Run and Stop + + "Run" start an oscilloscope like display of a moving sine curve + "Stop" stops it. + + While the plot os running (or not) 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. + + 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, but you can see the outline of the former picture. + + I'd like the cursor to change as you change tools, but the stock + wx.Cursors 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. + + + Any bugs, comments, feedback, questions, and especially code are welcome: + + -Chris Barker + + Chris.Barker@noaa.gov + + """ + + def OnInit(self): + frame = DrawFrame(None, wx.ID_ANY, "Plotting Test",wx.DefaultPosition,wx.Size(700,400)) + + self.SetTopWindow(frame) + + return True + + +if __name__ == "__main__": + + app = DemoApp(0) + app.MainLoop() + + diff --git a/samples/floatcanvas/YDownDemo.py b/samples/floatcanvas/YDownDemo.py new file mode 100755 index 00000000..76b7c891 --- /dev/null +++ b/samples/floatcanvas/YDownDemo.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python + +""" +This demonstrates how to use FloatCanvas with a coordinate system where +Y is increased down, instead of up. This is a standard system for +images, for instance. + +Note that there are some problems with doing this for bitmaps and text: things that require positioning. + +I'm sure it can be fixed, but I don't have a need for it, so I haven't taken the time yet. +-chb + +""" + +import wx + +## import the installed version +from wx.lib.floatcanvas import NavCanvas, FloatCanvas + +## import a local version +#import sys +#sys.path.append("..") +#from floatcanvas import NavCanvas, FloatCanvas + + +import numpy as N + +def YDownProjection(CenterPoint): + return N.array((1,-1)) + +class DrawFrame(wx.Frame): + + """ + A frame used for the FloatCanvas Demo + + """ + + def __init__(self, *args, **kwargs): + wx.Frame.__init__(self, *args, **kwargs) + + self.CreateStatusBar() + + # Add the Canvas + Canvas = NavCanvas.NavCanvas(self,-1, + size = (500,500), + ProjectionFun = YDownProjection, + Debug = 0, + BackgroundColor = "DARK SLATE BLUE", + ).Canvas + + self.Canvas = Canvas + + Point = (0,0) + Box = Canvas.AddRectangle(Point, + (80,100), + FillColor = "blue" + ) + + Canvas.AddText("%s"%(Point,), Point, Position="cr") + Canvas.AddPoint(Point, Diameter=3, Color = "red") + + + Point = (0,100) + Canvas.AddText("%s"%(Point,), Point, Position="cr") + Canvas.AddPoint(Point, Diameter=3, Color = "red") + + FloatCanvas.EVT_MOTION(self.Canvas, self.OnMove ) + + + self.Show() + Canvas.ZoomToBB() + + def OnMove(self, event): + """ + Updates the status bar with the world coordinates + """ + self.SetStatusText("%.2f, %.2f"%tuple(event.Coords)) + + +app = wx.App(False) +F = DrawFrame(None, title="FloatCanvas Demo App", size=(700,700) ) +app.MainLoop() + + + + + + + + + + + + + diff --git a/samples/floatcanvas/white_tank.jpg b/samples/floatcanvas/white_tank.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6e49f4c8bc98ad047bbd9dc354ec56399c2197ab GIT binary patch literal 166242 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 H-;e(bGUXlo literal 0 HcmV?d00001