How to stretch placeholder to parent group

We have a parent group containing multiple child groups which have stretch set to horizontal but they do not take up the whole width of the parent.

this placeholder is in the parent’s template.

 $(
          go.Panel,
          'Horizontal',
          { stretch: go.GraphObject.Horizontal, alignment: go.Spot.Center },
          $(go.Placeholder, {
            padding: 0,
            alignment: go.Spot.TopCenter,
            minSize: new go.Size(430, 0),
            stretch: go.GraphObject.Horizontal
          })
        ),

you can see the child groups highlighted in red.
I want the second inner group to take up the whole available area but can’t achieve this without explicitly changing the width on model change which obviously isn’t the right way to achieve this.

the child group’s template starts like →

$(

      go.Group,

      'Auto',

      {

        background: 'transparent',

        ungroupable: true,

        movable: false,

        computesBoundsAfterDrag: true,

        deletable: false,

        selectionAdorned: false,

        selectable: true,

        // when the selection is dropped into a Group, add the selected Parts into that Group;

        // if it fails, cancel the tool, rolling back any changes

        // Groups containing Nodes lay out their members vertically

        layout: $(go.TreeLayout, {

          angle: 90,

          layerSpacing: 10

        })

      },

      $(go.Shape, 'Rectangle', { fill: null, stroke: 'none', strokeWidth: 0 }),

      $(

        go.Panel,

        'Vertical',

        { minSize: new go.Size(430, 0), stretch: go.GraphObject.Horizontal },

In the snippet of code that you show, you are trying to horizontally stretch a Placeholder within a “Horizontal” Panel, which itself is stretching horizontally within whatever panel it is in.

First, unless you have deleted some relevant code here, it doesn’t make sense to use a “Horizontal” Panel with only one element in it. Delete that panel and just use the placeholder.

Second, it is really the responsibility of the Group.layout to determine the position and size of all of its member nodes. A “Horizontal” Panel cannot affect the positions and sizes of other Parts.

So the question is: what is the relevant Group.layout?

@walter
for the parent group we are using a simple gridlayout like this →


layout: $(go.GridLayout, {

          wrappingColumn: 1,

          alignment: go.GridLayout.Position,

          sorting: go.GridLayout.Ascending,

          comparer: (sectionA, sectionB) => {

            if (sectionA.data.order < sectionB.data.order) {

              return -1;

            }

            if (sectionA.data.order > sectionB.data.order) {

              return 1;

            }

            return 0;

          },

          cellSize: new go.Size(1, 1),

          spacing: new go.Size(4, 4)

        })

OK, the GridLayout is not going to change the width of any of the Nodes that it is laying out, so you aren’t going to get the effect that you want.

Maybe you want something like: Uniform Column Layout

@walter can you sort this layout? we depend on sorting to place the sections in the correct order

core.mjs:6469 ERROR Error: Trying to set undefined property “sorting” on object: Layout()

You’ll need to enhance the implementation of UniformColumnLayout.doLayout to sort the Parts before laying them out.

@walter ok I can do that on my end. The thing about your Uniform Column Layout is that it’s relying on setting a width (500) on inner nodes whereas our solution has a dynamically changing width within the section itself.

So what determines the minimum width? I’m wondering if you have to get the width of every “leaf” group (i.e. without any nested groups) so that you can compute the minimum needed.

@walter that’s sorta what I was doing before I reached out except I was doing it on model change.
Seems like I need to get the width of the contents of each group to see if it’s greater than my minimum width and use that for each sub group to make it fit the parent.

Will your leaf groups have a Group.layout that is not single-column oriented? And all of the groups that have nested groups will use a single-column layout? The normal behavior is that each layout operates on nodes whose size is known. That includes nodes that are groups because those nested groups will have had their layouts already performed.

Yes we do have nodes that have known widths at the base level which seem to trickle up to the containing group’s actualBounds properties and we have minSize set on all groups as well in case they’re empty etc… I’m getting very close to getting it to stretch dynamically now as you can see from gif but seems there’s some redraw issues that are seemingly random.

redrawIssues

@walter is there a way to force a redraw that’s not too expensive? the doLayout in the group correctly lays out the group but it seems links are out of place as you can see from the above gif. The issue fixes itself by hovering over any node but this is obviously not ideal.

I don’t see that in the GIF, but it’s going too quickly for me to see everything.

What are the properties of the Links?

Are you making any changes outside of a transaction?

What happens when the user hovers? What are they hovering over when things are “fixed”? What mouse-oriented event handlers have you defined?

We’ve resolved the issue using a custom layout based on gridLayout where super.doLayout is called twice: once before finding the max width and once after setting all the child members width to the max width.

doLayout(coll: go.Diagram | go.Group | go.Iterable<go.Part>): void {
    if (this.wrappingColumn !== 1) {
      throw Error(
        'wrappingColumn must equal 1 for layout type UniformColumnLayout'
      );
    }

    var diagram = this.diagram;
    diagram.startTransaction(Transactions.LayoutTransactionName);

    const allparts = this.collectParts(coll)
      .toArray()
      .sort(this.comparer);

    allparts.forEach(part => {
      if (part.category === Category.Section) {
        part.width = NaN;
      }
    });

    // first pass using base grid layout to get desired sizes
    super.doLayout(coll);

    let widestSectionWidth = LayoutConstants.sectionMinWidth;
    allparts.forEach(part => {
      if (part.category === Category.Section) {
        const partWidth = part.actualBounds.width;
        if (partWidth > widestSectionWidth) {
          widestSectionWidth = partWidth;
        }
      }
    });

    allparts.forEach(part => {
      if (part.category === Category.Section) {
        part.width = widestSectionWidth;
      }
    });

    // second pass to update for adjusted sizes
    super.doLayout(coll);

    diagram.commitTransaction('Layout');
  }

Thanks for the update. That looks like a better solution.

I assume your constructor sets wrappingColumn to 1. But it’s good to have that check there.