Implementing "Layout Completed" Event Listener with Arranging Layout

Hello, I am trying to combine Arranging layout with the example here:
https://gojs.net/latest/samples/arrowheads.html

My question is, how should I be accessing the “actualCenter” attribute of the primary layout inside of the “LayoutCompleted” DiagramEvent listener. Alternatively, if there is a better/different method I should use to implement the desired node layout.

layout: $(ArrangingLayout,
                { // create a circular arrangement of circular layouts
                    primaryLayout: $(go.CircularLayout, {


                    }),  // must specify the primaryLayout
                    arrangingLayout: $(go.CircularLayout, {
                        spacing: 50,
                        aspectRatio: .7,
                        arrangement: go.CircularLayout.ConstantDistance,
                        nodeDiameterFormula: go.CircularLayout.Circular,
                        direction: go.CircularLayout.Counterclockwise,
                        sorting: go.CircularLayout.Forwards
                    }),

                    // Uncommenting this filter will force all of the nodes and links to go into the main subset and thus
                    // will cause all those nodes to be arranged by this.arrangingLayout, here a CircularLayout,
                    // rather than by the this.sideLayout, which by default is a GridLayout.
                    filter: function (part) { return true; },

                    // called for each separate connected subgraph
                    preparePrimaryLayout: function (lay, coll) {  // color all of the nodes in each subgraph
                        var root = null;  // find the root node in this subgraph
                        coll.each(function (node) {
                            if (node instanceof go.Node && node.findLinksInto().count === 0) {
                                root = node;
                            } 
                        });
                    },

                    prepareSideLayout: function (lay, coll, b) {  // called once for the sideLayout
                        // adjust how wide the GridLayout lays out
                        lay.wrappingWidth = Math.max(b.width, this.diagram.viewportBounds.width);
                    },
                }),

                // define a DiagramEvent listener
                "LayoutCompleted": function (e) {
                    // now that the CircularLayout has finished, we know where its center is
                    var cntr = dia.findNodeForKey("Center");
                    cntr.location = dia.layout.actualCenter;
                }

If your situation is like the Arrowheads sample, there is a particular node, which I will call the “root” node, in each subnetwork that is laid out by the ArrangingLayout.primaryLayout. You don’t want that root node to be put in the circle, but you want to put that node at the center of the circle.

So the first step is to make sure the root node is not positioned by the CircularLayout in the ArrangingLayout.preparePrimaryLayout function:

                    if (root !== null) {
                      root.isLayoutPositioned = false;
                      lay.root = root;
                      // ...

We save the reference to that “root” node on the layout so that at the end of the layout we can put it at the center. That can be accomplished in an override of CircularLayout.commitNodes:

                  primaryLayout: $(go.CircularLayout,
                    {
                      commitNodes: function() {
                        go.CircularLayout.prototype.commitNodes.call(this);
                        if (this.root) {
                          this.root.location = this.actualCenter;
                          this.root.isLayoutPositioned = true;
                          this.root = null;
                        }
                      }
                    }),

This also depends on the node template setting Part.locationSpot to go.Spot.Center.

Gotcha, moving in the right direction. When I try to override the commitNodes function however I get the following error:

TS2445|(TS) Property ‘commitNodes’ is protected and only accessible within class ‘CircularLayout’ and its subclasses.

Im also getting a prompt pointing me towards the page:
https://gojs.net/latest/intro/extensions.html
to find instructions on how to properly override this function.

My Code:

primaryLayout: $(go.CircularLayout, { 
                        commitNodes: function () {
                            go.CircularLayout.prototype.commitNodes.call(this);
                            if (this.root) this.root.location = this.actualCenter;
                        },
                        spacing: 50

                    }),  // must specify the primaryLayout
                    arrangingLayout: $(go.CircularLayout, {
                        spacing: 50,
                        aspectRatio: .7,
                        arrangement: go.CircularLayout.ConstantDistance,
                        nodeDiameterFormula: go.CircularLayout.Circular,
                        direction: go.CircularLayout.Counterclockwise,
                        sorting: go.CircularLayout.Forwards
                    }),

Ah, my code is ES5, not TypeScript, so I was just taking the shortcut of overriding by clobbering the object’s instance method.

You can just define a simple class extending CircularLayout.

I think I understand, how would I extend just the primary layout’s CircularLayout? I imagine that we dont want to override the arrangingLayout’s commitNodes function.

If you are asking how to define a subclass and override a method: Extending GoJS -- Northwoods Software

So I extended the Circular layout class and created an override of the commitNodes class. The only thing that is confusing me is how I should pass the definition of root into the new override class that I have created.

export class CustomCircularLayout extends go.CircularLayout {
    // new data properties (fields) get declared and initialized here

    constructor() {
        super();
        // other initializations can be done here
    }

    
    public commitNodes() {
        if (this.root) this.root.location = this.actualCenter;
    }

}

You haven’t declared “root” as a new property of your class, so that it can be set by preparePrimaryLayout. (see my original code, above)

Ok, that resolved my issue to where the website is compiling and loading successfully again. However, when the graph loads in I get:

image

I think now my issue is that the commitNodes function isnt being called or that I removed the “LayoutCompleted” DiagramEvent listener. Either way, the root nodes of the two sub graphs arent being placed correctly resulting in this.

Any Ideas?

Thanks!

EDIT: What appears to be happening is that the root nodes of the Primary layout are being laid out but the rest of the nodes are being ignored.

Odd, you don’t have any links at all? So basically everything should be laid out by the ArrangingLayout.sideLayout (which defaults to a GridLayout)?

But in that case I don’t understand why all of those pink nodes were arranged in a circle, presumably by a CircularLayout. Unless you set ArrangingLayout.filter to always return true in order to avoid using the sideLayout. In which case the ArrangingLayout.primaryLayout is basically useless, because it would always be operating on a single node each time.

And in that case I don’t understand why there is a gap in your circle and the orange and yellow nodes are located up at the top-left. Unless those are the “root” nodes that you have moved?

I did set ArrangingLayout.filter to true so that all of the primary nodes would be laid out in a circle even if I had not added in the sub node data yet. Should I attach my code to here so that you can get a better look at whats happening.

Those are the “root” nodes that have their layoutposition frozen with:

lay.root.isLayoutPositioned = false;

Here is what it looks like before I switch to using the custom layout with the previous flag commented again.
image

Switching back to the custom Layout I get this
image

I had modified a copy of the extensions/Arranging.html sample.

With the changes I had specified before, Implementing "Layout Completed" Event Listener with Arranging Layout - #2 by walter, I then added the setting of ArrangingLayout.filter to force the ArrangingLayout.sideLayout not to be used at all. The result:

Isn’t this what you wanted? Hmmm, a CircularLayout of only three nodes doesn’t work so well when moving one of the nodes to the center of the two-node layout. But it seems to work well with five or more nodes.

Yeah, this is exactly what I was trying to achieve. I am using most of the code from that example but with different Node/Link data. Rather could the issue be from me defining specific links instead of using the {key: parent:} data format from the example?

The Arranging sample uses a TreeModel, not a GraphLinksModel. GoJS Using Models -- Northwoods Software

However, the type of model has no effect on layouts, since layouts only operate on Parts (Nodes and Links). Of course using a TreeModel means you cannot have any non-tree-structured graphs, but that doesn’t matter to the layout.

I tried your last suggestion and I think its working now.

image

While I understand that using so few nodes isnt optimal is there anything that I can do to force “Alpha” to be positioned correctly.

Like so:
image

Even when adding nodes to the right cluster I encounter the same thing:

image

Can you think of anything that would cause the root node to be placed like that?

I can’t explain that. Check your code to make sure that when it sets this.root.location = this.actualCenter that it is not setting the wrong node’s location.

Also, note the bug fix I added to my implementation of commitNodes, above.

Ill check that out, in the meanwhile I was experimenting with using Force Directed Layout and I might be able to get this to work for us

Thanks for all the help!