Align a child node with it's upstream node when they are in a different groups

We are using GoJS to create network schematics, we use Nodes to represent equipment, Groups to represent equipment containment (e.g. buildings / cabinets) and also to allow collapsing of sub networks and Links to represent connections between equipment.

We are currently using the TreeLayout layout for out diagram as well as our groups and we have found that the placement of nodes is generally good but is not that great when the upstream Node is in a different group to the child node. GoJS is laying out the Sub Graphs independently of the nodes connected upstream/ downstream of the Group, this leads to undesirable layouts when the upstream group is very different in size to the downstream group.

In the example below we would like for the child nodes to be vertically aligned with their parent nodes even when the parent is contained in a different group (unless there are multiple children for the parent in which case they should be as close as possible) . Do you have any suggestions for a layout customization that would achieve this?

before adding groups - notice that the leaf nodes are nicely aligned

after adding the groups

Try setting Group.layout to null in your Group template.

Thank you looks good :)

I’ve noticed that now the groups for siblings nodes can overlap vertically. Is there a way to allow the layout to ensure that siblings have enough space that their containing groups don’t overlap? I can see that there is the nodeSpacing option but this seems like a blunt instrument as it will force the spacing whether we have containing groups or not.

Maybe you want to set TreeLayout.compaction to go.TreeLayout.CompactionNone.

I already have it set, here is our laout:

 $(FlatTreeLayout, { 
		angle: 0, 
		layerSpacing: 20 ,
		compaction : go.TreeLayout.CompactionNone,
		sorting : go.TreeLayout.SortingAscending,
		comparer: function(va, vb) {
			var da = va.node.data;
			var db = vb.node.data;
			if (da.orderSeq < db.orderSeq) return -1;
			if (da.orderSeq > db.orderSeq) return 1;
			return 0;
		  }
		});

You can see that the groups are being ignored by the layout code:

I want the nodes to layout nicely with respect to their parents but I also don’t want the groups to overlap. I can easily deal with the horizontal overlap, for the vertical overlap I don’t want to fix this by increasing nodeSpacing as this would mandate a large gap for all siblings (not just the ones with large groups). Is there a way to dynamically determine the group vertical size and adjust the sibling separation to deal with it?

You may want to override TreeLayout.assignTreeVertexValues so that you can assign TreeVertex.layerSpacing and/or TreeVertex.nodeSpacing for individual vertex objects corresponding to Node that belong to a different Group from the adjacent vertexes/nodes.

Thank you, I will look into it now

I have another issue:

To determine the TreeVertex.layerSpacing I need to determine the difference between the trailing right coordinate of the parent and its containing group (which is not common with the vertexes groups) and difference between the leading left coordinate of the vertex and its containing group (which is not common with the parents groups). e.g.: GetNonCommonGroup(v.parent).actualBounds.right - GetNonCommonGroup(v).actualBounds.right.

To determine the TreeVertex.nodeSpacing I need to know the difference in the top coordinate between the vertex and it’s containing group (which is not shared by other siblings of the vertex).

Unfortunately at the time of assignTreeVertexValues the actualBounds of the groups only have valid width and height and cannot be used to determine the top, right or left coordinates. Also the width of the containing groups is sometimes less than the width of the vertex and the vertex.node.acutalBounds.width.

This example intentionally sets TreeLayout.nodeSpacing to zero and TreeLayout.layerSpacing to 20, as reasonable minimum values, so that you can see the artificial spacing created by the override of TreeLayout.assignTreeVertexValues.

function init() {
  const $ = go.GraphObject.make;

  myDiagram =
    $(go.Diagram, "myDiagramDiv",
      {
        layout: $(go.TreeLayout,
          {
            nodeSpacing: 0,  // to demonstrate making more room for a child inside a different group
            layerSpacing: 20,
            assignTreeVertexValues: function(v) {
              if (this.angle !== 0) return;
              const par = v.parent;
              if (v.node) {
                const group = v.node.containingGroup;

                // increase height of vertex if the next sibling is in a different group
                if (par) {
                  const arr = par.children;
                  const idx = arr.indexOf(v);
                  if (idx < arr.length-1) {
                    const sib = arr[idx+1];
                    if (sib.node && sib.node.containingGroup !== group) {
                      v.height += 20;
                    }
                  }
                }

                // increase width of vertex if there are any children in a different group
                const children = v.children;
                let diff = false;
                for (let c of children) {
                  if (c.node && c.node.containingGroup !== group) {
                    diff = true;
                    break;
                  }
                }
                if (diff) v.width += 20;
              }
            }
          })
      });

  myDiagram.nodeTemplate =
    $(go.Node, "Auto",
      //{ width: 100, height: 50 },
      $(go.Shape,
        { fill: "white", portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer" },
        new go.Binding("fill", "color")),
      $(go.TextBlock,
        { margin: 8, editable: true },
        new go.Binding("text").makeTwoWay())
    );

  myDiagram.groupTemplate =
    $(go.Group, "Auto",
      { layout: null },
      $(go.Shape,  { fill: "whitesmoke", stroke: "lightgray" }),
      $(go.Placeholder, { padding: 10 })
    );

  myDiagram.model = new go.GraphLinksModel(
  [
    { key: 1, text: "Alpha", color: "lightblue" },
    { key: 2, text: "Beta", color: "orange" },
    { key: 22, text: "Beta", color: "orange" },
    { key: 3, text: "Gamma", color: "lightgreen", group: -1 },
    { key: 33, text: "Gamma", color: "lightgreen", group: -1 },
    { key: 4, text: "Delta", color: "pink" },
    { key: 44, text: "Delta", color: "pink" },
    { key: 41, text: "Delta", color: "pink" },
    { key: 414, text: "Delta", color: "pink" },
    { key: 5, text: "Epsilon", color: "yellow", group: -2 },
    { key: 55, text: "Epsilon", color: "yellow", group: -3 },
    { key: 56, text: "Epsilon", color: "yellow", group: -3 },
    { key: 57, text: "Epsilon", color: "yellow", group: -3 },
    { key: -1, isGroup: true },
    { key: -2, isGroup: true },
    { key: -3, isGroup: true },
  ],
  [
    { from: 1, to: 2 }, { from: 2, to: 22 },
    { from: 1, to: 3 }, { from: 3, to: 33 },
    { from: 1, to: 4 }, { from: 4, to: 44 },
    { from: 1, to: 41 }, { from: 41, to: 414 },
    { from: 1, to: 5 }, { from: 5, to: 55 }, { from: 5, to: 56 }, { from: 5, to: 57 },
  ]);
}

image

Thanks for the reply Walter, your example seems to be adjusting the width and the height using hard coded constants (i.e. 20).

Some of the groups I have to deal with are large and others are quite small, some are only large in one dimension and not the other. I would like to determine the (possible) area of intersection between the groups / nodes and adust the layout accordingly. I don’t know how to calculate the overlap during TreeLayout.assignTreeVertexValues.

If you look at the screen capture I included on Oct 22 you will see an example of a group that overlaps another group horizontally over the space of 3 nodes. I have other examples even worse than this, I don’t want to have to use a worst case buffer that will leave the rest of the diagram looking very sparse.

At assignTreeVertexValues no nodes have been positioned yet, so naturally there’s no way to tell how large any group will be.

Yes, the 20 was hard-coded, but it just depends on the margin/padding provided by the group template. Apparently you are using a larger value.

It is going to be hard to determine the group’s dimensions at assignTreeVertexValues.

Here is one example group:

Here is a long one:

I think there may be no easy way to estimate the group bounds during assignTreeVertexValues, are there any other places I can customise that will allow me to move the groups horizontally and vertically and easily be able to adjust their surrounding nodes/ groups?

You cannot estimate the size of any group, but you should know how much of a margin the group will add beyond its Placeholder.