Clustering subtrees in force directed subtrees

I’m trying to make subtrees of a force directed layout more discernible, preferably through distancing them from the parent cluster. I figured the best way would be to manipulate the length of the edge leading to an expanded subtree, but the documentation isn’t exactly clear on the matter of networks. The force directed layout, when initialized does not create a network. If I set up a network like this:

  var network = new go.ForceDirectedNetwork();
  network.addParts(diagram.nodes);

  for (var i=0; i < diagram.model.nodeDataArray.length; i++) {
    var vertex = new go.ForceDirectedVertex();
    var node = diagram.findNodeForData(diagram.model.nodeDataArray[i]);
    vertex.node = node;

    network.addVertex(vertex);
  }

  for (var i=0; i < diagram.model.linkDataArray.length; i++) {
    var edge = new go.ForceDirectedEdge();
    var link = diagram.findLinkForData(diagram.model.linkDataArray[i]);
    edge.link = link;

    network.addEdge(edge);
  }

  diagram.layout.network = network;

I get a proper network with all the vertexes and edges, but manipulating them changes nothing, even after invalidating the layout. The only direct example in the documentation (doLayout in Layout class) suggests that a network has to be constructed from scratch every time you’d want to make a change. Is that the intended process? Also, that example suggests that passing the diagram should set the network up properly, but I always get an empty network when doing it this way.

For normal usage, you need to override doLayout or makeNetwork or specific methods of the layout, to do any customization.

Yes, with your code you are creating a network, adding vertexes corresponding to diagram nodes twice, adding a bunch of disconnected edges, and then setting the Layout.network, which as you point out, will just throw what you created away and use its own.

See for example the DemoForceDirectedLayout class in Force Directed Layout. That sets a property on each vertex of the network that is created by default in the base method.

Here’s an example that overrides ForceDirectedLayout specific methods in order to customize the behavior:

  function CustomLayout() {
    go.ForceDirectedLayout.call(this);
  }
  go.Diagram.inherit(CustomLayout, go.ForceDirectedLayout);

  CustomLayout.prototype.electricalCharge = function(v) {
    if (v.edgesCount <= 1) return 1;
    else return this.defaultElectricalCharge;
  }

  CustomLayout.prototype.gravitationalMass = function(v) {
    if (v.edgesCount <= 1) return 0;
    else return 1;
  }

  CustomLayout.prototype.gravitationalFieldX = function(x, y) {
    var w = this.diagram.viewportBounds.width;
    if (x < -w / 3) return 100;
    if (x > w / 3) return -100;
    return 0;
  }

  CustomLayout.prototype.gravitationalFieldY = function(x, y) {
    var h = this.diagram.viewportBounds.height;
    if (y < -h / 3) return 100;
    if (y > h / 3) return -100;
    return 0;
  }

Try this. You might want to fiddle with the constants, though.

Thank you, Walter, that clears the issue up. For anyone looking to solve a similar problem in the future, the constants I ended up with are the following:

CustomForceLayout.prototype.electricalCharge = function(v) {
  if (v.edgesCount <= 1) return 70;
  else return this.defaultElectricalCharge;
}

CustomForceLayout.prototype.springLength = function(v) {
  if (v.toVertex.edgesCount >= 2) return 1650;
  else return 50;
}