Arranging Layout for groups

We are trying to use Arranging Layout in our diagram. It works well as expected for the nodes which are not inside any groups (diagram’s top level nodes).

However, the nodes inside any group don’t align properly with Arranging layout, the overlap with each other.

We have set the diagram and groups layout as ArrangingLayout and initialised it with below parameters.

{
        primaryLayout: $(go.LayeredDigraphLayout, {
              direction: 0,
              layerSpacing: 10,
              isInitial: false,
              isOngoing: false,
              columnSpacing: 10
            }),  // must specify the primaryLayout
        arrangingLayout: $(go.LayeredDigraphLayout, {
          direction: 0,
          layerSpacing: 10,
          isInitial: false,
          isOngoing: false,
          columnSpacing: 10
        }),
  
        prepareSideLayout: function (lay: go.GridLayout, coll: go.Set<go.Part>, b: go.Rect) {  // called once for the sideLayout
          // adjust how wide the GridLayout lays out
          const self = this as any;
          if (self.diagram) lay.wrappingWidth = Math.max(b.width, self.diagram.viewportBounds.width);
        }
}

Is it possible to use Arrangin Layout as a group layout? If yes, can you help us with a code snippet?

I’ll work on a sample for you later today.

[EDIT] I should point out that having the arrangingLayout be a LayeredDigraphLayout doesn’t really take advantage of that layout, because by default it will only be operating on disconnected subnetworks. You could just as well use a GridLayout with GridLayout.wrappingColumn set to 1. Or just set ArrangingLayout.arrangingLayout to null, because LayeredDigraphLayout already behaves that way for disconnected subnetworks.

1 Like

I suspect the problem is that using legacy JavaScript overriding of methods won’t work for the Group.layout because groups are copied. In other words, you may have set properties that are methods on an instance of a layout for the Group.layout, but it won’t get copied when the group template is copied in order to instantiate the Group for a node data in the model.

If you define a subclass instead of doing what the Arranging Layout sample does, it seems to work OK when the layout is of a group’s members:

<!DOCTYPE html>
<html><body>
  <div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:800px; min-width: 200px"></div>

  <script src="https://unpkg.com/gojs"></script>
  <script src="https://unpkg.com/gojs/extensions/ArrangingLayout.js"></script>

  <script id="code">
class CustomArrangingLayout extends ArrangingLayout {
  constructor() {
    super();
    // additional custom properties for use by preparePrimaryLayout
    this._colors = ["red", "orange", "yellow", "lime", "cyan"];  // possible node colors
    this._colorIndex = 0;  // cycle through the given colors
  }

  // called for each separate connected subgraph
  // color all of the nodes in each subgraph
  preparePrimaryLayout(lay, coll) {  // method override requires function, not =>
    var root = null;  // find the root node in this subgraph
    coll.each(node => {
      if (node instanceof go.Node && node.findLinksInto().count === 0) root = node;
    });
    var color = "white";  // determine the color for the nodes in this subgraph
    if (root !== null) {
      // root.key will be the name of the class that this node represents
      // Special case: "LayoutNetwork", "LayoutVertex", and "LayoutEdge" classes are "violet"
      if (root.key.indexOf("Layout") === 0 && root.key.length > "Layout".length) {
        color = "violet";
      } else {  // otherwise cycle through the Array of colors
        color = this._colors[this._colorIndex++ % this._colors.length];
      }
    }
    coll.each(node => {  // assign the fill color for all of the nodes in the subgraph
      if (node instanceof go.Node) {
        var shape = node.findObject("SHAPE");
        if (shape !== null) shape.fill = color;
      }
    });
  }

  // called once for the sideLayout
  prepareSideLayout(lay, coll, b) {  // method override requires function, not =>
    // adjust how wide the GridLayout lays out
    lay.wrappingWidth = b.width;
  }
}


const $ = go.GraphObject.make;

function makeLayout() {
  return $(CustomArrangingLayout,
    { // create a circular arrangement of circular layouts
      primaryLayout: $(go.CircularLayout),  // must specify the primaryLayout
      arrangingLayout: $(go.CircularLayout, { nodeDiameterFormula: go.CircularLayout.Circular, spacing: 30 }),
      sideLayout: $(go.GridLayout, { wrappingWidth: 1000 }),

      // Uncommenting this filter will force all of the nodes and links to go into the main subset and thus
      // will cause all those nodes to be arranged by this.arrangingLayout, here a CircularLayout,
      // rather than by the this.sideLayout, which by default is a GridLayout.
      //filter: part => true,
    });
}

myDiagram =
  $(go.Diagram, "myDiagramDiv",  // create a Diagram for the DIV HTML element
    {
      initialAutoScale: go.Diagram.Uniform,
      layout: makeLayout()
    });

myDiagram.nodeTemplate =
  $(go.Node, go.Panel.Auto,
    $(go.Shape, { name: "SHAPE", figure: "RoundedRectangle", fill: "lightgray" },
      new go.Binding("fill", "color")),
    $(go.TextBlock, { margin: 2, textAlign: "center" },
      new go.Binding("text", "key", s => {
        // insert newlines between lowercase followed by uppercase characters
        var arr = s.split("");
        for (var i = 1; i < arr.length-1; i++) {
          var a = arr[i-1];
          var b = arr[i];
          if (a === a.toLowerCase() && b === b.toUpperCase()) {
            arr.splice(i, 0, "\n");
            i += 2;
          }
        }
        return arr.join("");
      })));

myDiagram.groupTemplate =
  $(go.Group, "Auto",
    { layout: makeLayout() },
    $(go.Shape, { fill: null }),
    $(go.Placeholder)
  );

myDiagram.linkTemplate =
  $(go.Link,
    { layerName: "Background" },
    $(go.Shape));

// Collect all of the data for the model of the class hierarchy
const nda = [ { key: "GROUP", isGroup: true }];
const lda = [];

// Iterate over all of the classes in "go"
for (var k in go) {
  var cls = go[k];
  if (!cls) continue;
  var proto = cls.prototype;
  if (!proto) continue;
  proto.constructor.className = k;  // remember name
  // find base class constructor
  var base = Object.getPrototypeOf(proto).constructor;
  if (base === Object) {  // "root" node?
    nda.push({ key: k, group: "GROUP" });
  } else {
    // add a node for this class and a tree-parent reference to the base class name
    nda.push({ key: k, group: "GROUP" });
    lda.push({ from: base.className, to: k });
  }
}

// Create the model for the hierarchy diagram
myDiagram.model = new go.GraphLinksModel(nda, lda);
  </script>
</body></html>

Noted. Will set ArrangingLayout.arrangingLayout to GridLayout.

Thanks for the sample. I tried to create a subclass CustomArrangingLayout as per the sample but the nodes inside group still overlap with each other.

We can’t help you unless you provide screenshots and relevant code.