FamilyTree layout

Never mind. It does “snap to grid”. I do have another MAJOR problem that has been keeping me from getting GoDiagram from working just the way I need it to.

Basically, when I am trying to add parents to a node that is at the very top of my grid instead of positioning the new parents at the very top and "pushing" all of the other nodes downward to make room it puts the 2 new nodes at random positions next to or beneath the existing child that had the parents added to it and looks something like this:
Legend:
1. Green lines represent marriage link.
2. Red Lines represent daughter link.
3. Blue lines represent son link.
4. Red circles represent females.
5. Blue squares represent males.
The original nodes were 1 & 2 as the parents of female child 3. Then parents 4 & 5 were added to node 1.
As you can see, instead of inserting the new generation of parents at the top and creating a normal orthogonal design it wrapped the nodes around the top elements.
Of all the samples you guys provide, I believe the family tree was the closest in concept to what I need to do (although it was pretty far off). However, the problem with the way it generates the tree is that it assumes the lower the nodeid the higher up in the tree it is. In other words it doesn't take into account that nodes may be added "upwards" for previous generations as well as "downwards".
Either way I have been working on this on and off for about 5 months and my deadline is fast approaching. Is there any way to accomplish what I am trying to do?
Thanks in advance.

You can change the code in FamilyTreeDoc.LayoutNodes not to look for the PersonNode with the lowest ID. If you already know a “root” node, use it. If you don’t, you’ll need to walk the whole diagram to assign generations to each PersonNode, and then find a Parent-less PersonNode that is in the oldest generation.

I was afraid you’d say something like that.

Since there technically isn't going to always be 1 root node (i.e. when people "marry into" a family then all of the new in-laws and their relatives sort of screw up that concept. Also, going with that concept, what if there are multiple Parent-less PersonNodes that are in the oldest generation?
Thanks Again.
-Ami
PS: So basically I would have to update the algorithm that looks for the root node to also check for the node's generation and each time I find a lower number for the generation I then reset the root node to that new node?

Well, assigning generations to each PersonNode shouldn’t be too hard.

Also, this is why people buy GoLayout. Some customization would be needed to identify spouses as being in the same level (i.e. generation) and to identify the children, since the links are coming out of the marriage link instead of a node. I thought we had done that (commented out), but now that I look at the code, apparently that was just for JGo.

My company does own full licenses (with source code) for GoDiagram and I believe GoLayout. Is there a number I can call to have someone walk me through what I need to do?

It’s an interesting test for GoLayout, so I’ll look into it as soon as I get a chance.

Thank you.

I really appreciate that.
It would be really fantastic if you guys got that functionality to work.

Walter,

I was wondering something. What determines the location of nodes? The rectangle/subrectangles or the action position values?
I am still trying to understand how each level in a diagram works. Is there a different subrectangle for each new level that contains the next generation of nodes?
-Ami
You can position any GoObject by setting its Position or Location property. Positions/locations/sizes are (X,Y) floating point numbers in "document" coordinates, which are different than the integer "view" or Control coordinates, since GoViews support translation (scrolling) and scaling (zooming).
No, levels are not contained or enclosed by any GoObjects -- each node and each link is a top-level GoObject in the GoDocument. (Actually in a GoLayer in the GoDocument.)

OK, here’s a replacement FamilyTreeDoc.cs. You’ll need to add a reference to Northwoods.Go.Layout.dll, copy the licenses.licx file from LayoutDemo, and uncomment out the #define.

/*<BR> * Copyright © Northwoods Software Corporation, 1998-2007. All Rights<BR> * Reserved.<BR> *<BR> * Restricted Rights: Use, duplication, or disclosure by the U.S.<BR> * Government is subject to restrictions as set forth in subparagraph<BR> * (c) (1) (ii) of DFARS 252.227-7013, or in FAR 52.227-19, or in FAR<BR> * 52.227-14 Alt. III, as applicable.<BR> */ // If you want to use GoLayoutLayeredDigraph to do the family tree layout,<BR>// uncomment the following line, just in this file:<BR>//#define GOLAYOUT using System;<BR>using System.Drawing;<BR>using System.Collections;<BR>using Northwoods.Go;<BR>#if GOLAYOUT<BR>using Northwoods.Go.Layout;<BR>#endif namespace FamilyTree {<BR> /**<BR> * FamilyTreeDoc is a GoDocument that manages the GoObjects used to display<BR> * family tree information in a GoView.<BR> * <p><BR> * The primary purpose is to convert the database information in a GenDB<BR> * object (with its Persons) into GoObjects: PersonNodes and GoLinks<BR> * representing marriages and resultant children.<BR> * <p><BR> * There are three kinds of top-level objects in the document:<BR> * a PersonNode representing Person,<BR> * a GoLabeledLink representing a marriage between two Persons,<BR> * a GoLink linking a child with the marriage between its mother and father.<BR> * <p><BR> * For each of these three kind of objects, there are "Find..." and "Get..."<BR> * methods: FindNode(), GetNode(), FindMarriageLink(), GetMarriageLink(),<BR> * FindChildLink(), and GetChildLink(). The "Find..." versions just look<BR> * for an existing object representing the Person or relationship between<BR> * Persons. The "Get..." versions return either the existing object or a<BR> * newly created GoObject corresponding to the given Person or relationship.<BR> * <p><BR> * This design is unusual in that it the label of a marriage link is not<BR> * a GoLinkLabel (a GoText), but a GoPort, which in turn is attached to<BR> * links to all of that marriage's children.<BR> * <p><BR> * There are also methods to lay out the PersonNodes in the document in<BR> * a potentially reasonable way, starting with older generations at the<BR> * top, placing spouses next to each other, with their children below them.<BR> */<BR> [Serializable]<BR> public class FamilyTreeDoc : GoDocument {<BR> public FamilyTreeDoc() {<BR> this.DB.Load();<BR> this.LinksLayer = this.Layers.CreateNewLayerBefore(this.Layers.Default);<BR> } public void InitNodes() {<BR> // Iterate over all the Persons in the database<BR> foreach (DictionaryEntry entry in this.DB.GetHashtable()) {<BR> Person p = entry.Value as Person;<BR> if (p == null) continue; // Make sure the PersonNode exists for the Person<BR> PersonNode pn = GetNode(p); // Get the mother and create the PersonNode for her<BR> // if such a Person exists<BR> Person mother = this.DB.FindPerson(p.mother);<BR> PersonNode mothernode = GetNode(mother); // Get the father and create the PersonNode for him<BR> // if such a Person exists<BR> Person father = this.DB.FindPerson(p.father);<BR> PersonNode fathernode = GetNode(father); // Create a child link<BR> GetChildLink(fathernode, mothernode, pn); // Create any marriage links for this Person<BR> ArrayList marriages = p.spouseslist;<BR> for (int i = 0; i < marriages.Count; i++) { // Person stores references to other persons not<BR> // as pointers, but as integer identifiers<BR> int spouseID = (int)marriages[i]; // Get the spouse as a Person<BR> Person spouse = this.DB.FindPerson(spouseID); // Make sure the PersonNode exists for the spouse<BR> PersonNode spousenode = GetNode(spouse); // Now we can make sure the GoLink exists between them<BR> GetMarriageLink(pn, spousenode);<BR> }<BR> } // try to position all the PersonNodes nicely<BR> LayoutNodes(); // add a caption<BR> // don't care about undo/redo, so don't need to call fireForedate here<BR> GoComment caption = new GoComment();<BR> caption.Text = <BR> "Some of the Tudors of English royalty in the 1500's.\r\n" +<BR> "\r\n" +<BR> "Women are pink; men are blue; marriage links are green.\r\n" +<BR> "\r\n" +<BR> "Tooltips provide more information about each person.\r\n" +<BR> "DELETE to remove selected people from the tree;\r\n" +<BR> "CTRL-L to reposition all the people;\r\n" +<BR> "INSERT to start over with the original tree;\r\n" +<BR> "CTRL-P to print; CTRL-Q to quit.";<BR> caption.Editable = false;<BR> caption.Label.Editable = false;<BR> caption.Position = new PointF(250, 380);<BR> Add(caption);<BR> } <BR> // If we already know about a PersonNode for a given object,<BR> // return it, else return null<BR> public PersonNode FindNode(Person key) {<BR> if (key == null) return null;<BR> Object val = myMap[key];<BR> return val as PersonNode;<BR> } // Get a PersonNode for a given object, using an existing one<BR> // if possible, otherwise creating one<BR> public PersonNode GetNode(Person key) {<BR> if (key == null) return null;<BR> PersonNode node = FindNode(key);<BR> if (node == null) {<BR> node = new PersonNode();<BR> node.Person = key; // initialize and keep back pointer<BR> // PersonNodes are always in front of any links<BR> Add(node);<BR> // keep track of Person --> PersonNode mapping<BR> myMap[key] = node;<BR> }<BR> return node;<BR> } // Override the standard document behavior to clean up the entry<BR> // in the hash table from Person to PersonNode<BR> public override void Remove(GoObject obj) {<BR> if (obj is PersonNode) {<BR> PersonNode node = (PersonNode)obj;<BR> Object key = node.Person;<BR> if (key != null)<BR> myMap.Remove(key);<BR> }<BR> base.Remove(obj);<BR> } // Override the standard document behavior to clean up all the<BR> // entries in the hash table from Person to PersonNode<BR> public override void Clear() {<BR> myMap.Clear();<BR> base.Clear();<BR> } // If we can find a marriage link between the two given people,<BR> // return it<BR> public GoLabeledLink FindMarriageLink(PersonNode p1, PersonNode p2) {<BR> if (p1 == null) return null;<BR> if (p2 == null) return null; // marriage links are always connected at the TextNode's BottomPort<BR> GoPort p1p = p1.BottomPort;<BR> GoPort p2p = p2.BottomPort;<BR> foreach (IGoLink l in p1p.Links) {<BR> GoLabeledLink link = l as GoLabeledLink;<BR> if (link == null) continue;<BR> GoPort other = link.GetOtherPort(p1p) as GoPort;<BR> if (other == p2p)<BR> return link;<BR> }<BR> return null;<BR> } // Create a marriage link between the two given people if it does<BR> // not already exist<BR> public GoLabeledLink GetMarriageLink(PersonNode p1, PersonNode p2) {<BR> if (p1 == null) return null;<BR> if (p2 == null) return null; GoLabeledLink l = FindMarriageLink(p1, p2);<BR> if (l == null) {<BR> // marriage links are always connected at the TextNode's BottomPort<BR> GoPort p1p = p1.BottomPort;<BR> GoPort p2p = p2.BottomPort;<BR> // create a labeled link, with the middle label being a port!<BR> l = new GoLabeledLink();<BR> l.FromPort = p1p;<BR> l.ToPort = p2p;<BR> l.Selectable = false;<BR> l.RealLink.Pen = MarriageLinkPen;<BR> GoPort midp = new GoPort();<BR> midp.Size = new SizeF();<BR> midp.Style = GoPortStyle.None;<BR> midp.EndSegmentLength = 20;<BR> midp.FromSpot = GoObject.MiddleBottom;<BR> midp.ToSpot = GoObject.MiddleBottom;<BR> l.MidLabel = midp;<BR> // marriage links are always behind any nodes<BR> this.LinksLayer.Add(l);<BR> }<BR> return l;<BR> } // To improve the appearance of child links, the port on the<BR> // marriage link for the child links can be at either end as<BR> // well as at the middle<BR> public GoPort GetMarriageLinkPortForChild(GoLabeledLink link) {<BR> GoPort mp = (GoPort)link.MidLabel;<BR> if (mp == null)<BR> mp = (GoPort)link.ToLabel;<BR> if (mp == null)<BR> mp = (GoPort)link.FromLabel;<BR> return mp;<BR> } // If we can find a child link between the given person and a marriage<BR> // link between the two given parents, return it<BR> public GoLink FindChildLink(PersonNode p1, PersonNode p2, PersonNode c) {<BR> if (p1 == null) return null;<BR> if (p2 == null) return null;<BR> if (c == null) return null; // make sure there's a marriage between p1 and p2<BR> GoLabeledLink m = FindMarriageLink(p1, p2);<BR> if (m == null) return null;<BR> GoPort mp = GetMarriageLinkPortForChild(m); // look at c's parentport's link<BR> GoPort cp = c.TopPort;<BR> foreach (IGoLink l in cp.Links) {<BR> GoLink link = l as GoLink;<BR> if (link == null) continue;<BR> // found a child link--is it the right one?<BR> GoPort cmp = link.GetOtherPort(cp) as GoPort;<BR> if (cmp == mp)<BR> return link;<BR> }<BR> return null;<BR> } // Return an existing child link, if any, or else make sure a<BR> // marriage link exists between the two parents and then create a<BR> // child link from the marriage link to the child node<BR> public GoLink GetChildLink(PersonNode p1, PersonNode p2, PersonNode c) {<BR> if (p1 == null) return null;<BR> if (p2 == null) return null;<BR> if (c == null) return null; GoLink cl = FindChildLink(p1, p2, c);<BR> if (cl == null) {<BR> GoLabeledLink m = GetMarriageLink(p1, p2);<BR> GoPort mp = GetMarriageLinkPortForChild(m);<BR> // parent port is always the TopPort<BR> GoPort cp = c.TopPort;<BR> cl = new GoLink();<BR> cl.FromPort = mp;<BR> cl.ToPort = cp;<BR> cl.Selectable = false;<BR> if (c.Person.IsMale)<BR> cl.Pen = SonLinkPen;<BR> else if (c.Person.IsFemale)<BR> cl.Pen = DaughterLinkPen;<BR> else<BR> cl.Pen = Pens.Black;<BR> // child links are always behind any nodes<BR> this.LinksLayer.Add(cl);<BR> }<BR> return cl;<BR> } // Place all the people where they belong. Start with the parent-less<BR> // person with the lowest ID, call layoutTree() for that person, and<BR> // then call layoutTree() repeatedly while unplaced people remain. #if GOLAYOUT<BR> public void LayoutNodes() {<BR> GoLayoutLayeredDigraph layout = new FamilyLDAL(this);<BR> layout.PerformLayout();<BR> }<BR>#else<BR> public void LayoutNodes() {<BR> RectangleF rect = new RectangleF(10, 10, 0, 0); // initial top-left position RaiseChanging(GoDocument.AllArranged, 0, null); PersonNode root = null;<BR> int lowestID = 99999999; // move all nodes so they appear unpositioned<BR> foreach (GoObject obj in this) {<BR> if (obj is PersonNode) {<BR> PersonNode node = (PersonNode)obj;<BR> node.Position = new PointF(0, 0);<BR> Person p = node.Person;<BR> if ((p.number < lowestID) &&<BR> node.TopPort.LinksCount == 0) {<BR> root = node;<BR> lowestID = p.number;<BR> }<BR> }<BR> } // start with Person with lowest ID having no parents<BR> LayoutTree(root, ref rect);<BR> rect.X = rect.X + rect.Width;<BR> rect.Width = 0; // find all parentless, unpositioned known person nodes and lay them out<BR> foreach (GoObject obj in this) {<BR> if (obj is PersonNode) {<BR> PersonNode node = (PersonNode)obj;<BR> Person p = node.Person; if (!IsPositioned(node) &&<BR> node.TopPort.LinksCount == 0) {<BR> LayoutTree(node, ref rect);<BR> rect.X = rect.X + rect.Width;<BR> rect.Width = 0;<BR> }<BR> }<BR> } RaiseChanged(GoDocument.AllArranged, 0, null, 0, null, NullRect, 0, null, NullRect);<BR> this.Bounds = ComputeBounds();<BR> } // implement a simple tree-layout algorithm<BR> // ORIGRECT is modified to reflect the resultant placements<BR> private void LayoutTree(PersonNode node, ref RectangleF origrect) {<BR> if (IsPositioned(node))<BR> return; float spousewidth = 0; RectangleF childrect = new RectangleF();<BR> childrect.X = origrect.X + origrect.Width;<BR> childrect.Y = origrect.Y + node.Height + VertSeparation;<BR> childrect.Width = 0;<BR> // childrect.height is ignored // iterate through all the person's marriages<BR> GoPort outp = node.BottomPort;<BR> foreach (IGoLink l in outp.Links) {<BR> GoLabeledLink link = l as GoLabeledLink;<BR> if (link == null) continue;<BR> GoPort spousep = link.GetOtherPort(outp) as GoPort;<BR> PersonNode spousenode = (PersonNode)spousep.Parent; // parent group is a PersonNode // iterate over the children of this marriage<BR> GoPort mp = GetMarriageLinkPortForChild(link);<BR> // put the marriageportforchild near the spouse, to handle<BR> // multiple spouses more reasonably<BR> if (link.FromPort == spousep)<BR> link.FromLabel = mp;<BR> else if (link.ToPort == spousep)<BR> link.ToLabel = mp;<BR> // now look at each child<BR> foreach (IGoLink cl in mp.Links) {<BR> GoLink childlink = cl as GoLink;<BR> if (childlink == null) continue;<BR> GoPort childp = childlink.GetOtherPort(mp) as GoPort;<BR> PersonNode childnode = (PersonNode)childp.Parent; LayoutTree(childnode, ref childrect);<BR> } // now position the spouse immediately below the node<BR> if (!IsPositioned(spousenode)) {<BR> spousenode.Position =<BR> new PointF(origrect.X + origrect.Width + spousewidth + HorizSeparation,<BR> origrect.Y + node.Height);<BR> spousewidth += spousenode.Width + HorizSeparation;<BR> }<BR> } // figure out the maximum width for the node, all the spouses,<BR> // and all the children<BR> float parentwidth = Math.Max(node.Width, spousewidth);<BR> float subtreewidth = Math.Max(parentwidth, childrect.Width); // place this node, centered<BR> node.SetSpotLocation(GoObject.MiddleTop,<BR> new PointF(origrect.X + origrect.Width + subtreewidth/2 + HorizSeparation,<BR> origrect.Y)); // place all the spouses, centered<BR> if (spousewidth < subtreewidth) {<BR> // figure out horizontal offset<BR> float offset = (subtreewidth - spousewidth)/2;<BR> foreach (IGoLink l in outp.Links) {<BR> GoLabeledLink link = l as GoLabeledLink;<BR> if (link == null) continue;<BR> GoPort spousep = link.GetOtherPort(outp) as GoPort;<BR> PersonNode spousenode = (PersonNode)spousep.Parent; // parent group is PersonNode // only adjust a spouse if it's located here<BR> if ((spousenode.Left >= origrect.X + origrect.Width) &&<BR> (spousenode.Left <= origrect.X + origrect.Width + subtreewidth))<BR> spousenode.Left = spousenode.Left + offset;<BR> }<BR> } // return the effective width of this subtree<BR> origrect.Width += subtreewidth + HorizSeparation;<BR> } private bool IsPositioned(PersonNode pn) {<BR> return (pn.Left >= 10 || pn.Top >= 10);<BR> } // node spacing<BR> public float VertSeparation = 80;<BR> public float HorizSeparation = 4;<BR>#endif // !GOLAYOUT public GenDB DB {<BR> get { return myDB; }<BR> } // link parameterization<BR> public Pen MarriageLinkPen = new Pen(Color.Green, 2);<BR> public Pen DaughterLinkPen = Pens.Red;<BR> public Pen SonLinkPen = Pens.Blue; // the "genealogical database"<BR> private GenDB myDB = new GenDB(); // map Persons to PersonNodes<BR> private Hashtable myMap = new Hashtable();<BR> } #if GOLAYOUT<BR> public class FamilyLDAL : GoLayoutLayeredDigraph {<BR> public FamilyLDAL(GoDocument doc) {<BR> this.DirectionOption = GoLayoutDirection.Down;<BR> this.ColumnSpacing = 3;<BR> this.Document = doc; // customize the Network to reflect the real relationships<BR> GoLayoutLayeredDigraphNetwork net = new GoLayoutLayeredDigraphNetwork();<BR> foreach (GoObject obj in this.Document) {<BR> // each PersonNode gets a GoLayoutLayeredDigraphNetworkNode<BR> PersonNode n = obj as PersonNode;<BR> if (n != null) net.AddNode(n);<BR> }<BR> foreach (GoObject obj in this.Document) {<BR> // for each marriage link,<BR> GoLabeledLink marriage = obj as GoLabeledLink;<BR> if (marriage != null) {<BR> // create a link from each spouse to a dummy node representing the marriage<BR> GoPort mp = ((FamilyTreeDoc)this.Document).GetMarriageLinkPortForChild(marriage);<BR> GoLayoutLayeredDigraphNode mnode = net.CreateNetworkNode();<BR> mnode.GoObject = mp;<BR> net.AddNode(mnode);<BR> GoLayoutLayeredDigraphLink link;<BR> link = net.CreateNetworkLink();<BR> link.FromNode = net.FindNode(marriage.FromNode.GoObject);<BR> link.ToNode = mnode;<BR> net.AddLink(link);<BR> link = net.CreateNetworkLink();<BR> link.FromNode = net.FindNode(marriage.ToNode.GoObject);<BR> link.ToNode = mnode;<BR> net.AddLink(link);<BR> // then create a link from the dummy marriage node to each child<BR> foreach (GoLink l in mp.Links) {<BR> PersonNode child = l.GetOtherPort(mp).GoObject.ParentNode as PersonNode;<BR> if (child == null) return;<BR> link = net.CreateNetworkLink();<BR> link.GoObject = l;<BR> link.FromNode = mnode;<BR> link.ToNode = net.FindNode(child);<BR> net.AddLink(link);<BR> // and add a dummy link from the dummy marriage node to each child's spouse<BR> foreach (GoLabeledLink childm in child.BottomPort.Links) {<BR> PersonNode childspouse = childm.GetOtherNode(child).GoObject as PersonNode;<BR> if (childspouse == null) return;<BR> link = net.CreateNetworkLink();<BR> link.FromNode = mnode;<BR> link.ToNode = net.FindNode(childspouse);<BR> net.AddLink(link);<BR> }<BR> }<BR> }<BR> }<BR> this.Network = net;<BR> } public override void PerformLayout() {<BR> base.PerformLayout();<BR> foreach (GoObject obj in this.Document) {<BR> PersonNode n = obj as PersonNode;<BR> // shift up PersonNodes that have parents<BR> if (n != null && n.TopPort.LinksCount > 0) {<BR> n.Top -= n.Height;<BR> if (n.BottomPort.LinksCount == 1) {<BR> // also shift up their spouses, if they have only one<BR> foreach (GoLabeledLink spousem in n.BottomPort.Links) {<BR> PersonNode spouse = spousem.GetOtherNode(n).GoObject as PersonNode;<BR> spouse.Top -= spouse.Height;<BR> }<BR> }<BR> }<BR> }<BR> this.Document.Bounds = this.Document.ComputeBounds();<BR> }<BR> }<BR>#endif<BR>}

Where is the “Bounds” property defined?

Are you talking about GoDocument.Bounds? That’s a new property (I have forgotten which version, but I think 2.6) which is just the combination of the GoDocument.TopLeft and GoDocument.Size properties.

If my company purchased version 2.5.2 in September do we get free upgrades to 2.6.2?

If you purchased a subscription, yes.

Regarding the code you provided me above, how can I get rid of the "root" node?
It seems as if this "mystery" node always appears at the top of my family tree and then when the loop iterates through all of the "real" Person objects that are stored in my Hash table it makes them children of this phantom root node.
If you check out the picture below, you'll see that at the very top of the tree there are 2 squares. One is gray and the other green. Since the smaller square is the same color as a marriage link and seems to follow the larger gray square wherever I move it I can only assume that it is in fact a marriage link indicating that the root node is MARRIED TO ITSELF and it's children are the first two REAL Person objects in my Hash table. There are child links that stem from that root node to my first REAL Person nodes that are son and daughter links (i.e. red and blue). I have put an X through those 2 links indicating that they should not be there b/c the root node at the top should not be there either.
Still trying to figure out how to get rid of "phantom root node".

I don’t think the FamilyTree sample application has a single root node that is “married” to itself, so that must be something that you have created in your application, along with the parent links to “n/a” and “AQW5”.