Virtualized Graph with ForceDirectedLayout

If there are 50k nodes and links, does VirtualizedForceLayout loops through all nodes?
Even though I am using virtualizedForceLayout, I am getting page went unresponsive

layout: $(VirtualizedForceDirectedLayout, {
randomNumberGenerator: { random() { return 0.5 } },
maxIterations: 100,
defaultSpringLength:10
},
“LayoutCompleted”: mergeLinksAtPorts,

Above are the diagram properties I use.

Virtualization saves on initialization time by avoiding the construction of a lot of Nodes and Links in the Diagram.

Virtualization does not save on layout time. I believe that ForceDirectedLayout just can’t handle such large graphs in a short time. It also depends on the nature of the graph.

For version 3, we have rewritten ForceDirectedLayout to produce better results, but even that might not be able to handle your case within a few seconds.

Is there any alternative ways to load large data by frequently appending to diagram?

Yes, you could try that. You might not need virtualization then, because presumably you’d start off with just hundreds rather than thousands.

Is there any chance you could partition the whole graph somehow? For example: Virtualized Packed Groups Layout

Yes, may be look like it.
Assume each node is a system.
A group of node will be in a location, and there are many locations.

I’m sorry, but I don’t understand what you are describing.

So, If I am able to partition, can I use virtualized packed layout?
Is that efficient than Force directed layout?

Well, yes, but PackedLayout isn’t as good as ForceDirectedLayout in arranging connected nodes near each other – in fact it ignores Links.

Please share working example of efficiently adding nodes, that would be helpful

Please understand that each time you add some nodes and links, you will probably want to perform another layout again, which will get slower and slower each time. Are you sure you want that behavior?

It’s better to do the layout on the server, or in a worker, than to wait for a layout to complete in the page. Once each node has a known location, you can use the regular virtualization sample: Virtualized Sample with no Layout

Could you please share the example for the above?

If you can use Node.js on the server, here’s a test script used to make sure that running a layout on a headless server (i.e. no Puppeteer) produces the right values for the node locations. Presumably you would save that location information in your database and send it to the browser. That way your Diagram’s Layout could have Layout.isInitial set to false, since it wouldn’t be required to run when receiving a new model and it already has all of the node locations.

This example depends on a TwoWay Binding on the Node.location property so that the laid-out locations are stored in the model. But you needn’t implement it that way – you could just iterate over the Nodes and save each one’s location or position or actualBounds or whatever is needed.

// nodescript.js
// This example loads the GoJS library, creates a Diagram with a layout and
// compares the updated model with the known correct result.

// Load GoJS.  This assumes using require and CommonJS:
const go = require("gojs");

const $ = go.GraphObject.make;  // for conciseness in defining templates

const myDiagram =
  $(go.Diagram, '', // No DOM, so there can be no DIV!
    {
      viewSize: new go.Size(400,400), // Set this property in DOM-less environments
      layout: $(go.LayeredDigraphLayout)
    });

myDiagram.nodeTemplate =
  $(go.Node, 'Auto',
    // specify the size of the node rather than measuring the size of the text
    { width: 80, height: 40 },
    // automatically save the Node.location to the node's data object
    new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
    $(go.Shape, 'RoundedRectangle', { strokeWidth: 0},
      new go.Binding('fill', 'color')),
    $(go.TextBlock,
      new go.Binding('text', 'key'))
  );

// After the layout, output results:
myDiagram.addDiagramListener('InitialLayoutCompleted', function() {
  const json = myDiagram.model.toJson();
  const expected = `{ "class": "GraphLinksModel",
  "nodeDataArray": [
{"key":"Alpha","color":"lightblue","loc":"0 25"},
{"key":"Beta","color":"orange","loc":"125 0"},
{"key":"Gamma","color":"lightgreen","loc":"125 75"},
{"key":"Delta","color":"pink","loc":"250 75"}
],
  "linkDataArray": [
{"from":"Alpha","to":"Beta"},
{"from":"Alpha","to":"Gamma"},
{"from":"Gamma","to":"Delta"},
{"from":"Delta","to":"Alpha"}
]}`;
  console.log(json === expected ? "test is OK" : "test got unexpected model: " + json);
});

// load a model:
myDiagram.model = new go.GraphLinksModel(
[
  { key: 'Alpha', color: 'lightblue' },
  { key: 'Beta', color: 'orange' },
  { key: 'Gamma', color: 'lightgreen' },
  { key: 'Delta', color: 'pink' }
],
[
  { from: 'Alpha', to: 'Beta' },
  { from: 'Alpha', to: 'Gamma' },
  { from: 'Gamma', to: 'Delta' },
  { from: 'Delta', to: 'Alpha' }
]);

Can I provide LinkTemplate also in the above code?
It also takes more time / sometimes unresponsive for even 2k nodes and 2k links.

You can provide the link template there, but why bother if you are only pre-computing the layout? In fact, for ForceDirectedLayout, don’t even bother setting the node template to something reasonable for a human to view. Just use something like:

myDiagram.nodeTemplate =
  $(go.Node, 
    // specify the size of the node rather than measuring the size of the text
    { width: 80, height: 40 },
    // automatically save the Node.location to the node's data object
    new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify)
  );

Remember that the point of doing this layout on the server ahead of time is to do it once and remember the result so that it doesn’t need to be done again by anyone at the time the user wants to see it.

Virtualized Sample with no Layout (gojs.net)
This virtualized Sample with no layout requires bounds to be in node data array.
Should I save bounds while generating the location in the server itself (bounds are not saving with makeTwoWay binding in the server) ?

I have two questions. Should I need bounds for virtualization. I strongly believe YES.
Can you please explain what is Quadtree ?
I need to implement it in angular. I am stuck in it.

Yes, that’s right – you should use the data.bound property to hold the rectangular area where the node should be. This node template might be more useful to you:

const NodeSize = new go.Size(120, 80);

myDiagram.nodeTemplate =
  $(go.Node,
    { desiredSize: NodeSize, locationSpot: go.Spot.Center },
    new go.Binding("background", "color"),
    new go.Binding("location", "bounds", b => go.Rect.parse(b).center)
        .makeTwoWay(loc => `${Math.round(loc.x) - NodeSize.width/2} ${Math.round(loc.y) - NodeSize.height/2} ${NodeSize.width} ${NodeSize.height}`)
  );

The complete Node.js code follows. You can try it by copying this into a layout.js file in a project where “gojs” has already been installed, and then running $ node layout.js.

const go = require("gojs");
const $ = go.GraphObject.make;

const myDiagram =
  new go.Diagram("",  // no DOM, so there can be no DIV
    {
      viewSize: new go.Size(400, 400),
      layout: new go.ForceDirectedLayout()
    });

const NodeSize = new go.Size(120, 80);

myDiagram.nodeTemplate =
  $(go.Node,
    { desiredSize: NodeSize, locationSpot: go.Spot.Center },
    new go.Binding("background", "color"),
    new go.Binding("location", "bounds", b => go.Rect.parse(b).center)
        .makeTwoWay(loc => `${Math.round(loc.x) - NodeSize.width/2} ${Math.round(loc.y) - NodeSize.height/2} ${NodeSize.width} ${NodeSize.height}`)
  );

myDiagram.addDiagramListener("InitialLayoutCompleted", () => {
  console.log(myDiagram.model.toJson());
});

myDiagram.model = new go.GraphLinksModel(
[
  { key: 1, text: "Alpha", color: "lightblue" },
  { key: 2, text: "Beta", color: "orange" },
  { key: 3, text: "Gamma", color: "lightgreen" },
  { key: 4, text: "Delta", color: "pink" }
],
[
  { from: 1, to: 2 },
  { from: 1, to: 3 },
  { from: 2, to: 2 },
  { from: 3, to: 4 },
  { from: 4, to: 1 }
]);

As far as Quadtree is concerned, it’s just an extension. If you are using Angular, you are probably using TypeScript. Copy the extensionsJSM/Quadtree.ts file into your project, and use it like any other code that you have. You may need to fix its import statement.

Hello @walter
I have implemented Virtualized Sample with no Layout (gojs.net) with locations pre-computed on the service in angular (It is slow while scrolling or zooming not smooth like in the example).
But my diagram looks like below (I have around 10k nodes and 10k links)

My linkTemplate has below properties :

isLayoutPositioned: false,
routing: go.Link.AvoidsNodes,
corner: 1

Please help me to improve the diagrams having no overlapping of nodes and links
Note : Also I have tried Asynchronous AvoidsNodes Routing (gojs.net). But It is not working as expected and it is slow while scrolling or zooming

If you don’t set those Link properties, either on the server or in the browser, how does it look?

I suspect you need to use v3.0 beta, for its improved ForceDirectedLayout.

If I don’t set those properties, it does look like below

BTW how to update to GOJS 3.0 version?