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.
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.
Well, assigning generations to each PersonNode shouldn’t be too hard.
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.
Walter,
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.
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”.