Center children in LayeredDigraphLayout (or use TreeLayout?)


#1

Hi Everyone,
We have based our graphic editor on the Flowgrammer sample from the GoWpfDemo.
The only issues we have with the layout, is that we want nodes to be aligned directly below the previous nodes with the exception of the circular nodes (join nodes) which we want to be aligned directly below their corresponding split nodes (question mark icon).
Is this possible using the LayeredDigraphLayout?
We tried using a TreeLayout instead and manually position the join nodes, but that had issues with overlaps and will force us to recursively calculate the correct amount of space to reserve for each If block (from split node to its join node) which is basically what the LayeredDigraphLayout does for us.
Any suggestions how to solve these issues?
Thanks


#2

I suppose you could override LayeredDigraphLayout.LayoutNodes to call the base method first and then explicitly move the “join” nodes horizontally to be where you want: vertically under the “split” node.

The only problem is that that assumes there’s not already a node there. I suppose it’s unlikely because there should be fewer nodes at the join layer, but that’s not guaranteed. If there is a Node already there, you can either just move the join node as close as possible without overlapping any nodes, or (more difficult) you can try shifting over all of the nodes in that layer to make room for the join node. Of course that might cause problems with any other subgraphs that might be in the shifted area…


#3

Hi Walter,
Thanks for the suggestion, but I don’t believe this is a good solution for me since there is really no guarantee that join nodes are indeed on layers with less nodes (guaranteed only in the very local branch of the tree/graph).
I think you helped someone achieve something similar in GoJS here: Center Children in Layout but the link for the code there is broken. Also the code is of course not WPF.
If there is no simple method to override in our specialization of LayeredDigraphLayout, I might have to revert to using the TreeLayout (our graph is basically a tree with join nodes) and manually calculate the space needed for each branch from the bottom up. Any other ideas before I do that?


#4

If your graph structure is indeed like a tree except for join nodes that only result in nested subgraphs, then yes, maybe you could use TreeLayout. I believe that code in GoJS, ParallelLayout, which is part of the kit now and thus no longer available in the temporary directory on the web site, uses nested Groups. But the use of Groups really should not be required.

If you can reliably find matching pairs of “split” and “join” nodes, I suggest that you subclass TreeLayout and override LayoutNodes to first call the base method and then move each “join” node to line up with the corresponding “split” node. You’ll want to set TreeLayout.Alignment to “CenterSubtrees” and TreeLayout.Compaction to “None”


#5

Hi Walter,
So I achieved a reasonable layout using the TreeLayout overriding LayoutTree and LayoutNodes to change the Bounds of special nodes and to shift the join nodes below their split nodes. An example of the result is below:

I kept the Block compacting mode as it seemed better in my opinion. The only issues I have now are with the links. As you can see from the pic, some of the links into the join nodes crossover other links and appear confusing. Also, the dashed links (from circular node to loop node) cross over other nodes, but using avoids nodes option is no good as they start meandering between the nodes and really clutter the diagrams. I would like for them to always be orthogonal and to the left of all nodes within the subtree. Unfortunately I couldn’t manage this by overriding LayoutLinks and adding a point to the route to the left of all subtree nodes.
Any suggestsions?


#6

Allowing subtree compaction may mean that there will be times when you will not be able to just move the merge/join node over to center it.

If you don’t want to use AvoidsNodes routing for those Links connecting with the merge/join nodes that you have moved, you’ll need to route them explicitly yourself. Sorry. But that will allow you to provide the desired X values in case of nested loop links.


#7

Hi Walter,
Need help figuring out when to manually route the link. I tried doing this in my override of LayoutLinks() after calling base, but this seems to have no effect.


#8

Actually, it works but when I move the node with the link or change the layout, the link is rerouted incorrectly.
I moved manually resetting the link’s points to UpdateRouteDataPoints() in my override of PartManager and it is set correctly at startup, but changing the node’s location only recalculates the link when its arrow head is clicked. Where can I put my manual code in order to ensure the link’s points are set correctly whenever it changes its location or when layout of the diagram is performed?


#9

Ok, got it. I just call link.Remeasure() after setting the route.
Sorry for replying to myself multiple times :flushed:


#10

Actually, you seem to be catching on pretty well.


#11

Hi Walter,
I’ve moved my rerouting code to handler for Diagram’s LayoutCompleted event (which is activated for most changes) and it works ok. My only problem is that when not all of the graph is within the view, links to nodes that have been moved by the layout process are not rerouted. This is the code I use:

private void myDiagram_LayoutCompleted(object sender, DiagramEventArgs e)
{
    foreach (Link link in this.myDiagram.Links)
    {
        this.rerouteLinkIfNeeded(link);
    }
}

private void rerouteLinkIfNeeded(Link link)
{
    if (link.Category.Equals("DashedLink"))
    {
        double LeftMostCoordinate = link.Bounds.X;
        //find the left most vertex in the loop's subtree 
        foreach (Part VDescendant in (link.ToNode.Data as LoopNodeData).ManipulationAlgorithm.GetBelongingParts(
            link.ToNode).Where(x => x != link))
        {
            //Node descendantNode = this.Diagram.PartManager.FindNodeForData(VDescendant, this.Diagram.Model);
            //TreeVertex DescendantVertex = v.Network.FindVertex(descendantNode);
            if ((VDescendant != null) && (VDescendant.Bounds.Left < LeftMostCoordinate))
            {
                LeftMostCoordinate = VDescendant.Bounds.Left;
            }
        }
        //Get the edge that connects the end block to the loop
        Route newRoute = new Route();
        newRoute.AddPoint(new System.Windows.Point(link.Route.Points[0].X, link.Route.Points[0].Y));
        newRoute.AddPoint(new System.Windows.Point(LeftMostCoordinate - 20, link.Route.Points[0].Y));
        newRoute.AddPoint(new System.Windows.Point(LeftMostCoordinate - 20, link.Route.Points.LastOrDefault().Y));
        newRoute.AddPoint(new System.Windows.Point(link.Route.Points.LastOrDefault().X, link.Route.Points.LastOrDefault().Y));
        link.Route = newRoute;
        link.Remeasure();
    }

This is an example of what I get after a layout and links that need rerouting are not in view:

You can see that the dashed links have not been rerouted as they should have. Manually activating a layout afterwards, with the links in view fixes the routing.
Any idea why this happens and how I can solve it?
Thanks
Orr


#12

I suspect you are subtly being affected by the minor virtualization that GoXam performs, to improve performance.

You can disable that by executing:

myDiagram.InitialLayoutCompleted += (s, e) => {
    myDiagram.Panel.Unsupported(23, false);
};

#13

Terrific! That solved my issue.
Thank you very much.