Maintain existing graph shape when appending items

This is primarily a question regarding the Force Directed layout, but I think it relates to all layouts.

I allow the user to build up a graph by adding new items to existing items (a node is selected, and the user then requests that related items are added to the graph - linking to the original node).

These graphs can get quite large, containing many nodes, and the users don’t like the general layout changing as they add in additional items (they have likely dragged items to a given place). To address this requirement, I have done the following:

  • once items are added to the graph (after layout complete) the nodes are marked as being in a fixed location (using node.isLayoutPositioned = false).
  • when adding new nodes to the graph (based on the user selecting an existing node and appending related nodes), I set the existing node to being layout positioned so that it can participate in the layout routine along with the new nodes that are being added.
  • Once the layout pass is completed I translate the nodes that participated in the layout by an amount that move the existing selected node back to its original location.

This seems a bit cumbersome. Is there a more appropriate way to keep the existing graph shape, while still allowing the user to append items to the graph and having the new items appear in close proximity to the ‘root’ node that they selected?

Don’t use Part.isLayoutPositioned. Set Layout.isOngoing to false. Layout | GoJS API

You can still invalidate Layouts if you want, including by calling Diagram.layoutDiagram(true).

thank you Walter.

I have set my diagram.layout.isOngoing = false if there are existing nodes on the graph. This maintains the existing node positions, but the items that I am adding do not appear. The new items have position and location values of Point(NaN,NaN) - as stated in the documentation they won’t go through the automatic layout pass.

What would be the correct approach to calculating positions for the current layout style so that I could manually set the position of the new items while maintaining the existing items position? I appreciate that if only some of the graph is involved in a layout pass that some items may overlap. I will select all of the newly added items and let the user move as a group if they obscure existing items.

Maybe you want to override ForceDirectedLayout.isFixed to be true for all of the “old” nodes and false for the “new” ones. See the Interactive Force sample for an example of overriding that method – where selected nodes are not moved by the ForceDirectedLayout but all other nodes are moved by it.

Note that the “isFixed” functionality is specific to ForceDirectedLayout to keep some nodes at fixed locations while still participating in the layout. Setting Part.isLayoutPositioned basically treats the Node or Link as if it were not visible or did not exist, as far as any Layout is concerned.

thank you Walter.

I will modify my code and try this approach. I will let you know how I get on :-)

Hi Walter,

just to let you know that your suggested change to use a custom ForceDirectedLayout has worked perfectly.

I created a custom layout with the minimum additional support that I needed, and added the ‘isExistingNode’ to the data bound to the node

    function StableForceDirectedLayout() {
        go.ForceDirectedLayout.call(this);
    }
    go.Diagram.inherit(StableForceDirectedLayout, go.ForceDirectedLayout);
    StableForceDirectedLayout.prototype.isFixed = function (v) {
        return v.node.data.hasOwnProperty("isExistingNode") && v.node.data.isExistingNode === true;
    }

The end result is a nice stable force directed graph. Newly added nodes are aware of the existing items, but the existing items keep their original position during the layout phase.

Thank you again for your help.