#!/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: %s" % 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 np ## 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 = 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 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 = np.asarray(XY, np.float).reshape(2,) WH = np.asarray(WH, np.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 = np.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 = np.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/ np.sqrt(3) Points = np.array(((0, c), ( L/2.0, -c/2.0), (-L/2.0, -c/2.0)), np.float64) 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.App(0) DrawFrame(None, -1, "FloatCanvas Tree Demo App", wx.DefaultPosition, (700,700) ) app.MainLoop()