ForcedDirectedLayout nodes are shrinking after loading

Hi,

I am using the ForcedDirectedLayout and I have bound the location to viewData using the following line :
new go.Binding("location", "viewData", go.Point.parse).makeTwoWay(go.Point.stringify).

We save the position of the nodes in the map using an API that saves my viewData into the DB.

But the problem is when I reload the map, I am getting the right viewData from DB, but goJs after reading the viewData, is shrinking all the nodes in the Map.

Even though I have moved two nodes far away and have stored the location, these nodes are coming closer when I reload it back.

Is there any config I am missing? Can you please help me with this?

When you are loading a model which has location data and corresponding Bindings for all of the node data, and when your Diagram.layout has been set to an instance of some Layout, you should set that layout’s Layout.isInitial to false. And maybe Layout.isOngoing too.

Please read https://gojs.net/latest/intro/layouts.html#LayoutInvalidation

Thanks Walter, this seemed to have solved the problem partially.

When i have the nodes which do not have the correct location data,
diagram.layoutDiagram(true) gets executed and they are placed correctly. Only problem is that after these nodes get placed correctly according to the layout, the location binding is not seeming to be happening.

I have used
new go.Binding("location", "viewData", go.Point.parse).makeTwoWay(go.Point.stringify)

After the diagram.layoutDiagram(true) is executed, my each nodes’ diagram.model.nodeDataArray[i].viewData is being null.

This is causing the problem where I need to updateTargetBinding which is bringing back all my nodes to center.

That Binding looks OK to me. But did you apply it to all of your Node templates? And did you apply it to each Node of each template and not to some object inside a Node?

  • Yes. Its applied to all of my node templates
  • I have applied it to each node in template not on objects inside node.

But still my viewData has nothing after diagram.layoutDiagram(true)

Odd, that doesn’t make sense at all. What are all of the uses of the identifier viewData throughout your app?

And you should never have to call updateTargetBindings.

If I am understanding this question correctly, we are using the viewData only to store the location of each node as decided by goJs layout to the backend. And this achieved by new go.Binding(“location”, “viewData”, go.Point.parse).makeTwoWay(go.Point.stringify).

[Mithun] Please see this link below :

I’m wondering if there is any code that is setting the viewData property. If the only place where that identifier is used is in that Binding, then that behavior of setting it to null cannot happen.

It might help to use go-debug.js to see if there are any other detectable problems.

There is nothing which is setting the viewData property.

Sorry, I forgot to mention this,
The backend when initially sends the model data, sends viewData as null.

Then when we load the diagram without using diagram.layoutDiagram(true) the viewData remains null. But if we dont use diagam.layoutDiagram(true), viewData will have the values set by the layout(having proper x and y values).

Am I missing anything here?

Ah – so there is some code setting that property to null.
If you initialize the value to undefined instead, perhaps you can avoid any problems. Or just don’t include the property, if that’s possible.

Hi Walter,

Initializing the viewData to undefined or deleting the viewData only from the array of objects that is being set to model.nodeDataArray is not helping. Infact, its not rendering any nodes on the map.
There is no other place in UI where we are setting the viewData.

To add to this, if diagram.layoutDiagram(true) is called. There are two observations I want to point out :

  1. di.model.nodeDataArray[i].viewData ===> undefined
  2. di.nodes.each(function(a){console.log(a.location.isReal())}) ===> I see this consoling true for all the nodes

All these nodes are having the real locations and they are not (0,0) but somehow they are not getting bound to viewData property of each node.

OK, so your node template has this Binding on the Node:
new go.Binding("location", "viewData", go.Point.parse).makeTwoWay(go.Point.stringify)

So, if you manually drag a node and then look at the model data, is that node’s node.data.viewData a string with what looks like about the right x y point value?

I just want to make sure that that binding is in fact declared correctly and works to save any changes to the model.

yes, correct.

Once we move the node and check node.data.viewData it has string with x and y point values.

Well, any layout will probably move some nodes, so their Node.location will be set, causing the TwoWay Binding to save a new string as the node data’s “viewData” property. But you say that if you call Diagram.layoutDiagram, the nodes do move yet the new locations are not saved. That doesn’t make sense to me. When and how are you calling Diagram.layoutDiagram? Maybe you’re calling it too early.

But if you are loading data, presumably you look at all of the nodeDataArray objects to check their “viewData” values. And you do so before setting Diagram.model, yes? In fact you need to set Layout.isInitial to true or false before you set Diagram.model.

Sorry for this, but its weird that its not working for me… may be my code flow and snippets might help us solve this.

var goJsDiagram =
    $$(go.Diagram, this.body.dom.id, {
        initialContentAlignment: go.Spot.Center, // center Diagram contents
        "undoManager.isEnabled": true, // enable Ctrl-Z to undo and Ctrl-Y to redo
        layout: $$(go.ForceDirectedLayout, {
            randomNumberGenerator: null
        }),
        allowHorizontalScroll: true,
        allowVerticalScroll: true,
        hasVerticalScrollbar: false,
        hasHorizontalScrollbar: false,
        "linkingTool.archetypeLabelNodeData": {
            category: "LinkLabel"
        },
        padding: 25
    });
goJsDiagram.layout.isInitial = false;
goJsDiagram.layout.isOngoing = false;
goJsDiagram.addDiagramListener('InitialLayoutCompleted', function(e) {
    //My logic to check if diagram.layoutDiagram(true) is needed
    if (e.diagram.nodes.all(function(n) { return (n.location.x === 0 && n.location.y === 0) })) {
        e.diagram.layoutDiagram(true);
    }
});
//Template map creation
//create two node templates and add it to diagram.nodeTemplateMap
//create two link templates and add it to diagram.linkTemplateMap
//Add nodes
model.nodeDataArray = myModel.nodeArray;
//Add links
model.linkDataArray = myModel.linkArray;

You see any problem with this flow?

To me it seems more natural to first set up the Diagram, just once, as your code does above.

Then whenever you want to load a new diagram/model, you first examine all of the node data objects to see if you need to do an initial layout or not. Then you just set:

// check all of the nodeDataArray objects to determine whether to do an initial layout or not
goJsDiagram.layout.isInitial = ...true or false...;
goJsDiagram.model = ...the new model using that nodeDataArray...;

No need for any “InitialLayoutCompleted” DiagramEvent listener. Really, that event is too late to decide whether to do a layout or not…

Thanks a lot!
This seems to be solving my problem.