Assigning nodes to layers and columns in LayeredDigraphLayout

Hi there,

I’m currently evaluating GoJS to see if it fits our company’s needs. We can potentially have hundreds of nodes linking to 1 - 3 nodes, and those 1 - 3 nodes could also link out to another large set of nodes.

I’m wondering if it’s possible to customize LayeredDigraphLayout to not have those hundreds of nodes stack into one single column, and instead to be more spread out amongst layers/columns, similar to GridLayout, but perhaps a bit more staggered to allow for complicated link routing.

Graph using LayeredDigraphLayout:

TreeLayout’s breadth limit option is pretty much what I’m looking for, but since nodes can have multiple parents and multiple children, some of which are shared amongst each other, I cannot use TreeLayout.

I’ve tried overriding assignLayers(), but it seems to only override the layer properties, and not the columns, which results in a layout that looks like a really tall staircase.

As you have already guessed, LayeredDigraphLayout does not support what you are looking for.

I suppose you could try putting all of the nodes in that long layer into a Group that has no visual representation. You could then set the Group.layout to do whatever you want. Maybe even a TreeLayout with a limited breadth? I’m just speculating…

Hmm, could you elaborate on how I could use TreeLayout that way?

I’ve tried applying TreeLayout with a limited breadth to the entire graph, but from what I’ve gathered, it only applies the breadth limit to children, because only the nodes in the last column get affected, and the nodes in the first column stay the same, like so:

So I think if I try your suggestion, to put the first column in a group and apply TreeLayout, I would still run into the same problem since those nodes are all parent/root-type nodes.

Is there any way I could apply a breadth limit to “parent” or “root” nodes?

OK. I started with the Beat Paths sample, http://gojs.net/latest/samples/beatPaths.html.

The only changes I made were in the creation of the model and specifying a Group template:

    myDiagram.groupTemplate =
      $(go.Group,
        $(go.Placeholder),
        { layout: $(go.GridLayout) }
      );

    // create the model and assign it to the Diagram
    var model =
      $(go.GraphLinksModel,
        { // automatically create node data objects for each "from" or "to" reference
          // (set this property before setting the linkDataArray)
          archetypeNodeData: {},
          // process all of the link relationship data
          linkDataArray: linkDataArray
        });

    // add a bunch of nodes that are linked normally,
    // but all those nodes also belong to a transparent Group:
    var groupdata = { isGroup: true };
    model.addNodeData(groupdata);

    for (var i = 0; i < 20; i++) {
      var nodedata = { group: groupdata.key };
      model.addNodeData(nodedata);
      model.addLinkData({ from: nodedata.key, to: "CAR" });
    }
    myDiagram.model = model;
  }

That results in this screenshot:

Note that the Group has no visuals, so you only see its member Nodes.

Thank you so much for the example!! This was incredibly helpful.

Is it possible to offset the starting position for every other row in GridLayout so that links are less likely to collide with nodes? Similar to how TreeLayout creates additional rows when it reaches the breadth limit–nodes aren’t horizontally aligned, but rather staggered in between the spaces of the nodes to the left of it.

You’d have to customize the layout to achieve such an effect. Or define your own Layout. http://gojs.net/latest/intro/extensions.html

You could use an instance of this for your Group.layout:

    function OffsetLayout() {
      go.Layout.call(this);
    }
    go.Diagram.inherit(OffsetLayout, go.Layout);

    OffsetLayout.prototype.doLayout = function(coll) {
      var w = this.diagram.viewportBounds.width;
      var x = 0;
      var y = 0;
      var row = 0;
      var maxHeight = 0;
      // COLL might be a Diagram or a Group or some Iterable<Part>
      var it = this.collectParts(coll).iterator;
      while (it.next()) {
        var node = it.value;
        if (node instanceof go.Node) {
          node.moveTo(x, y);
          x += node.actualBounds.width + 20; // spacing
          maxHeight = Math.max(maxHeight, node.actualBounds.height);
          if (x > w) {  // start next row
            // if it's an even row, indent the next row
            x = ((row % 2 !== 0) ? 0 : node.actualBounds.width / 2 + 10);
            y += maxHeight + 20;
            row++;
            maxHeight = 0;
          }
        }
      }      
    }

Wow, thank you so much for all your help, Walter!!! I’ve been applying all your suggestions to my code so far, and it’s been turning out great. I’m a lot more confident now that GoJS will suit our company’s needs.