Positioning sub trees relative to their parents

Greetings,

I am running Windows XP, .NET 3.5 and am using the GoLayoutTree to layout nodes in form of a decision
diagram. However, I am having difficulties to place whole subtrees
relative to their parents position. This is the code doing the
layouting:

public void DoLayout(GoDocument doc)
{
GoLayoutTree layout = new GoLayoutTree();
layout.Document = doc;
layout.Alignment = GoLayoutTreeAlignment.CenterSubtrees;
layout.Angle = 90;
layout.PerformLayout();
}

This layouts trees as following:

The problem is that I cannot control where conditional nodes place both of their subtrees. I want both conditional branches to not overlap their parents vertical left and right position. The following picture shows my desired results.

How can I manage such behaviour? Please be aware that the layouting must also work with conditional branches containing other conditional branches.

Thanks very much.

I don’t think you’re going to get what you want with GoLayoutTree.



Better to use GoLayoutLayeredDigraph, like our Flowgrammer sample does. To get the spacing between nodes under a conditional branch, increase layout.ColumnSpacing.

Thanks for the fast reply.

As a matter of fact, I have also tried using GoLayoutLayeredDiagraph, but received worse positioning as with the tree layout algorithm. While using the tree layout the most notable issue was that nodes in subtrees were put in the wrong column (the column under the conditional node itself) and I totally dislike the idea of adding dummy nodes. The code for the LayeredDiagram was as following:

GoLayoutLayeredDigraph layout = new GoLayoutLayeredDigraph();
layout.Document = this;
layout.DirectionOption = GoLayoutDirection.Down;
layout.PerformLayout();

I am aware that the layout.ColumnSpacing can do what I want, but as I said the ColumnSpacing depends on the parent’s node width and it again on its parent’s node width etc. Furthermore, the left branch of a condition does not necessarily be the same width as the same conditions right branch.

If the “Stop Timer” node were at least as wide as the conditional node, just as the “Start Testplan…” node is, I think this will work out better for you.

If you don’t want to change the actual size of the “Stop Timer” node, that’s OK – you can programmatically change the GoLayoutLayeredDigraphNode.Bounds and Focus properties of the GoLayoutLayeredDigraphNode that corresponds to that “Stop Timer” GoNode.

Walter, in simple test cases with only one conditional node your
approach works partly. But in more complicated cases with nested
conditions (every conditional node having its own width) the layouting
results in undefined positioning of nodes.

Can you point out what is wrong and why it wont work?

public class MyLayout: GoLayoutLayeredDigraph
{
    public MyLayout()
    {
        this.DirectionOption = GoLayoutDirection.Down;
        this.LayerSpacing = 10;
        this.LayeringOption = GoLayoutLayeredDigraphLayering.LongestPathSource;
        this.PackOption = GoLayoutLayeredDigraphPack.Median;
    }

    public override void PerformLayout()
    {
        GoLayoutLayeredDigraphNetwork network = this.Network;
        if (network == null)
            network = new GoLayoutLayeredDigraphNetwork(this.Document);

        foreach (GoObject obj in this.Document) {

            TestStepDisplayNodeIfElse _ifNode = obj as TestStepDisplayNodeIfElse;
            if (_ifNode != null) {

                TestStepDisplayNode _thenNode = GetThenNodeFromIfElse(_ifNode);
                TestStepDisplayNode _elseNode = GetElseNodeFromIfElse(_ifNode);
                GoLayoutLayeredDigraphNode ifNode = network.FindNode(_ifNode);
                GoLayoutLayeredDigraphNode thenNode = network.FindNode(_thenNode);
                GoLayoutLayeredDigraphNode elseNode = network.FindNode(_elseNode);

                if (thenNode != null) {
                    RectangleF tmp = ifNode.Bounds;
                    tmp.X += thenNode.Width;
                    thenNode.Bounds = tmp;
                }

                if (elseNode != null) {
                    RectangleF tmp = ifNode.Bounds;
                    tmp.X += elseNode.Width;
                    elseNode.Bounds = tmp;
                }
            }
        }

        this.Network = network;
        base.PerformLayout();
    }

    private TestStepDisplayNode GetThenNodeFromIfElse(TestStepDisplayNodeIfElse ifElseNode)
    {
        TestStepDisplayNode thenNode = null;

        if (ifElseNode != null && ifElseNode.LeftPort.Links.First != null && ifElseNode.LeftPort.Links.First.ToPort != null)
            thenNode = (TestStepDisplayNode) ifElseNode.LeftPort.Links.First.ToPort.Node;

        return thenNode;
    }

    private TestStepDisplayNode GetElseNodeFromIfElse(TestStepDisplayNodeIfElse ifElseNode)
    {
        TestStepDisplayNode elseNode = null;

        if (ifElseNode != null && ifElseNode.RightPort.Links.First != null && ifElseNode.RightPort.Links.First.ToPort != null)
            elseNode = (TestStepDisplayNode) ifElseNode.RightPort.Links.First.ToPort.Node;

        return elseNode;
    }
}

In my application I am using this layout as following:

// add nodes and links to GoDocument
MyLayout layout = new MyLayout();
layout.Document = this;
layout.PerformLayout();

I think what you want is to have each “then” node or “else” node be artificially as wide as the space needed for the corresponding subgraph for that condition, with a minimum being half the width of the “if” node.

The Flowgrammer sample has basically the same graph structure that I think you have, except that it also has additional “Output” nodes. Perhaps we can improve the Flowgrammer’s layout to do what I think you want.

I appreciate your help very much Walter!

No, I dont want subgraphs to appear directly under my condition itself, but rather align them in the next layer but further off to their left or right. Imagine the if-node to have another copy if it directly under the first one and each then/else node aligns to its boundaries plus a small offset. If a condition has more than one node in its depth each nodes’ center should line up with its parents center.

Well, I manually created a picture to show you what I desire. The picture is a bit imprecise, but the “save-result” node should line up also with its parents condition (the left most condition in the picture).

How about creating a GoLayoutFlowGrammer layout for the next GoLayout release? I think a lot of people could also use it.

I don’t understand your reasoning about the position of the “SaveResult” node.

If your graph structure is really a tree except for the merging links at the bottom, then maybe you can use GoLayoutTree. You need to set myTreeLayout.Compaction = GoLayoutTreeCompaction.None. But the positioning of “merge” nodes may still be an issue.

Ups, pardon me. I would like to place the “Save-Result” exactly in the middle of the farthest left and the farthest right node.

The tree layout was in general yielding better visual results, but I also see the benefits of the layered approach.

Like this?



Yes you are right, the positioning of nodes with multiple inlinks is
tricky with the GoLayoutTree. That’s the reason why I think the
GoLayoutLayeredDiagraph should be favored. Yet, I haven’t come up with
an working solution.

To clarify my desired output, I changed the picture above to look like what I want.

Almost Jake. I want no branch to overlap the bounds of its parent condition, nor do I want overlapping nodes at all.

In your example the whole else-branch of the first condition should be positioned farther over to the right, so far that the then-branch of the second condition does not overlap the else-branch if its first condition.

Imagine a barrier for each condition going toward to bottom of the diagram. Only in cases where nodes in deeper layers merge from two or more branches should the above rule be disengaged and those nodes be able to cross the barrier.

Ok, I added lines into the pictures to point this out, which I probably should have done in the very first picture…

I think I can also live with the idea of placing the “SaveResult” node directly under the top-most node, in my above case the “DefineVariable” node. But only if that implementation would simplify the overall algorithm complexity.

I can’t get the algorithm to leave as much space as your asking for. It really wants to pack things tighter than that. still investigting…

Jake, Walter. I found an algorithm doing it for my specific case, although I used part of the layered approach. First, I find all nodes with more than one subtree and recursively calculate each of its width/height. Each forking node is assigned to a level number. Then I position them in descending order.

However, I have a question. Lets say I have exactly one tree and assign each node its correct position in the tree. If I call ComputeBounds() does it return the width and height of the whole already positioned tree? This maybe important for me if I ever want to display multiple trees and require a certain margin between each of them).

GoDocument.ComputeBounds will give you the bounds of all the objects in the GoDocument.



ComputeBounds(IGoCollection coll, GoView view) can give you the bounds of a subset.



However… the actual positioning of nodes in Layout doesn’t happen until right at the end.