Stretch multiple nodes inside a group

I have a diagram with groups that contain multiple child groups. I’m trying to get all the child groups to stretch horizontally to fill the parent group.

I can reproduce my issue using this example: https://gojs.net/latest/samples/regrouping.html
And this model data:
{ “class”: “go.GraphLinksModel”,
“nodeDataArray”: [
{“key”:2, “text”:“Main 2”, “isGroup”:true, “category”:“OfGroups”},
{“key”:6, “text”:“Group D”, “isGroup”:true, “category”:“OfNodes”, “group”:2},
{“key”:7, “text”:“Group E”, “isGroup”:true, “category”:“OfNodes”, “group”:6},
{“key”:8, “text”:“Group F Long Name”, “isGroup”:true, “category”:“OfNodes”, “group”:6},
{“text”:“first D”, “group”:7, “key”:-14},
{“text”:“first E”, “group”:8, “key”:-15}
],
“linkDataArray”: [ ]}

Note that “Group E” doesn’t stretch to fill the horizontal space even though stretch: go.GraphObject.Horizontal is specified in the template.

Capture

Is there a simple way to do this? I saw another post mentioning using the Swim Lane example to write a custom layout manager, is that the best approach here?

The sizing of Nodes should be determined by the Layout. But all of the predefined layouts ignore Node.stretch because they do not want to change the size of any node. Off hand, I can only think of the TableLayout extension that tries to change the size of the nodes it lays out, based on the Node.stretch property.

I thought I had a sample demonstrating what I think you want, but I can’t find it at the moment. I’ll look some more soon.

Here’s the basic idea. Please understand the code and adapt it to suit your needs. The basic question is how you want the widths of the nested groups to be determined. How can the width decrease?

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

    JustifyingGridLayout.prototype.doLayout = function(coll) {
      this.arrangementOrigin = this.initialOrigin(this.arrangementOrigin);
      const allparts = this.collectParts(coll);

      var max = 0;
      allparts.each(function(p) {
        p.ensureBounds();
        if (p instanceof go.Group) max = Math.max(max, p.actualBounds.width);
      });
      var x = this.arrangementOrigin.x;
      var y = this.arrangementOrigin.y;
      allparts.each(function(p) {
        var h = p.actualBounds.height;
        if (p instanceof go.Group) p.width = max;
        p.moveTo(x, y);
        y += h + 10;  // vertical spacing
      });
    }

Use an instance of this layout as the value of Group.layout. Although it should work as the Diagram.layout too.

Thank you for the quick response!

I’m trying to modify this for my case. My goal is to let the go.GridLayout figure out the size of the top level group, then stretch all the sub groups horizontally to fill the available space in a single column.

This is what I have for the layout. I’m using this as the layout on the "OfGroup" template. The only other change I made was adding wrappingColumn: 1 to the GridLayout settings on the diagram layout:

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

JustifyingGridLayout.prototype.doLayout = function(coll) {
  const allparts = this.collectParts(coll);
   allparts.each(function(g) {
        g.ensureBounds();
        if (g instanceof go.Group) {
            stretchSubGroups(g.findSubGraphParts(), g.actualBounds.width);
        }
    });
}

function stretchSubGroups(parts, maxWidth) {
        parts.each(function(p) {
        p.ensureBounds();
        var w = maxWidth - (p.findSubGraphLevel() * 6); //parent width minus some space for padding
		p.width = w;
        if (p instanceof go.Group) {
            var children = p.findSubGraphParts();
            stretchSubGroups(children, w);
        }
    });
}

This is working pretty well for the example data in my post, but it doesn’t work in all cases. For example with this data:

{ "class": "go.GraphLinksModel",
"nodeDataArray": [
{"key":2, "text":"Main 2", "isGroup":true, "category":"OfGroups"},
{"key":6, "text":"Group D", "isGroup":true, "category":"OfNodes", "group":2},
{"key":7, "text":"Group E", "isGroup":true, "category":"OfNodes", "group":6},
{"key":8, "text":"Group F Long Name", "isGroup":true, "category":"OfNodes", "group":6},
{"key":9, "text":"Group D Long Name", "isGroup":true, "category":"OfNodes", "group":2},
{"text":"first D", "group":7, "key":-14},
{"text":"first E", "group":8, "key":-15},
{"text":"second D", "group":9, "key":-16}
 ],
  "linkDataArray": [  ]}

Group D Long Name is getting positioned on top of the other group for some reason.

image

Any suggestions are welcome.

The primary responsibility of any Layout is to position its nodes. Your code does not seem to do that – not for regular nodes and not for groups.

As you can see in the constructor, JustifyingGridLayout does not inherit from GridLayout. It would not make sense to change it to inherit from GridLayout when the code overrides doLayout.

If I can’t inherit from the GridLayout would I need to rewrite that logic to handle the positioning of groups? Right now the GridLayout is positioning the groups correctly, but not sizing them. The JustifyingGridLayout is sizing them correctly, but not positioning them.

Since your code does not inherit from GridLayout, no GridLayout code is doing anything for you.

Your code is not positioning each Node. See how in the code I gave you there is a call to Part.moveTo? That must be done in addition to the setting of width that you want to do.

I guess what I’m not understanding is why do I have to position the nodes myself when the GridLayout can already do that for me? (as seen in the original post). If I use the GridLayout, the position of the nodes is already exactly where I want them. All I am concerned with is changing their width.

Is this just a limitation of using a custom layout? Would it be possible to inherit from GridLayout (allowing it to position the nodes as it normally does) and then just change their size?

If you really want to use GridLayout, you can override GridLayout.commitLayers in which you assign the node widths.