Group area non consistent after resize

I have a group which is resizable, I also set dynamic ports on the group, similar to what is done in the Dynamic ports example. However, when I try to resize the group the right and bottom borders disappear (like they are cut off), along with the ports… Furthermore, when I Drag the node it may exit the groups area in those two directions. In the other two directions (top and left), the group resizes to reflect the change of nodes position… How can I fix this? Do I need to define a custom resizingTool?

What’s the Group.resizeObject? I.e. what did you assign to Group.resizeObjectName?

Maybe showing an outline of the visual tree of the group template would help discussion.

Hi, walter, I didn’t assign anything to Group.resizeObjectName…

This is basically the group template definition:

$(go.Group, 'Spot',
        {
            isSubGraphExpanded: true,
            resizable: true,
            ungroupable: true,
            doubleClick: doubleClicked,
            locationObjectName: 'BODY',
            locationSpot: go.Spot.Center,
            selectionObjectName: 'BODY'
        },
        $(go.Panel, 'Auto',
            {
                name: 'BODY',
                stretch: go.GraphObject.Fill,
                minSize: new go.Size(NaN, 50)
            },
            $(go.Shape, group.shape, group.details),
            $(go.Placeholder, {alignment: go.Spot.TopLeft}),
            $(go.TextBlock, textBlock,
                new go.Binding('text', 'text'))
        ),
        $(go.Panel, 'Vertical', {alignment: go.Spot.Left},
            new go.Binding('itemArray', 'inputsArray'),
            {
                itemTemplate: getItemTemplate(true)
            }
        ),
        $(go.Panel, 'Vertical', {alignment: go.Spot.Right},
            new go.Binding('itemArray', 'outputsArray'),
            {
                itemTemplate: getItemTemplate(false)
            }
        )

var getItemTemplate = function (isInput) {

    return $(go.Panel, 'Auto',
        {
            fromSpot: go.Spot.Right,
            fromLinkable: !isInput,
            fromLinkableDuplicates: false,
            toSpot: go.Spot.Left,
            toLinkable: isInput,
            toLinkableDuplicates: false,
            cursor: 'pointer'
        },
        new go.Binding('portId', 'portId'),
        $(go.Shape, 'RoundedRectangle',
            {
                strokeWidth: 2,
                margin: new go.Margin(1, 0),
                fill: 'lightblue',
                cursor: 'pointer'
            }),
        $(go.TextBlock, { margin: 2, textAlign: 'center' },
            new go.Binding('text', 'text'))
    )
};

Well, think about what it really is that you want the user to resize. I would guess there are some objects that you do not want to resize – they should just be positioned relative to the one object that should change size.

So you should give that one object a GraphObject.name and use that name as the value for Part.resizeObjectName. In your case you may have already given the central important thing a name.

http://gojs.net/latest/intro/tools.html#ResizingTool

That fixes the visual problem (the one cutting off the border of the group). However, before resizing, the group adjusts its size as I drag the inner nodes… Once I resize, it doesn’t do that when dragging nodes to the right and bottom (but still does for the left and top)… I guess I’ll need to implement a custom resizing tool to get the behaviour I want?

Have you read GoJS Sized Groups -- Northwoods Software ?

Basically, if your Group uses a Placeholder, you are telling it that the group’s location and size are determined by the area of its member nodes and links. That way as the user (or code) moves a member node, the group automatically resizes and repositions itself to fit around the new area occupied by the member nodes.

But if you are letting users change the size of a group, you are conflicting with the desire to allow the members of the group determine the size and position of the group. That doesn’t mean it isn’t possible, but it gets tricky.

There’s a separate problem in trying to keep a node from being moved outside of the area of a group. Read about Part.dragComputation – there’s an example of that.

And that’s yet different from the problem of resizing a group to avoid having a node go outside of the area of the group. Some samples implement this in a custom ResizingTool.

I read the documentation you provided…
Basically, it may be preferable that the user can determine the size and position of nodes inside the group by himself. According to that, a custom resizingTool would be the way to go.

IF I may ask, may be a related or unrelated question. Is there a way to prevent the user from dragging one node over the other? Or reposition nodes so that they don’t overlap?

DraggingTool | GoJS API gives an example of how to limit dragging nodes to remain inside a group. Variations of this are implemented by several samples.

Drag Unoccupied demonstrates custom dragging to avoid overlapping nodes.

More generally, most layouts avoid overlapping nodes.

Swim Lanes demonstrates both limiting drags to within a group and a custom ResizingTool to limit how small a group can be resized based on the contents and an absolute minimum.

I tried implementing a dragging behaviour that would allow the group to resize itself when a node is dragged out of bounds. Something similar to the shared states example.
However, it seems that the placeholder is the main source of my problems… just removing it would fix the resize issues. However, when I remove it and then try to group a selection of nodes, the nodes appear outside of the created group… they are parts of the group (I can verify that), but the group doesn’t position itself around its memberParts.

Yes, if you’re not going to use a Placeholder in the Group, you’ll need to determine its position and size yourself based on whatever policies you want to implement in your app.

Are there any samples on how to properly do this?

Call Diagram.computePartsBounds on the Group.memberParts, I suppose in a “SelectionMoved” or “PartResized” DiagramEvent listener.

Those events happen after the gesture is finished. If you want to update the position and size in realtime, you’ll need to override various methods of DraggingTool or ResizingTool.

There are examples of custom tools in the extensions and samples directories.

Just to clarify, you are talking about adjusting the group size and location after it has been created from a selection of nodes and links? When one is not using a placeholder.

Yes, you could do that in a “SelectionGrouped” DiagramEvent listener, which would happen at the end of a CommandHandler.groupSelection command.

Commands are not interactive, so there is no possibility for continuously adjusting the size and position of the new Group, because there is no tool continuously responding to mouse events to override any methods when creating a group…

Took me some time, but I decided to remove the placeholder from my group and manually set the size of the group and position… the size is se the following way:

   let setGroupSize = function (group) {

    let diagram = group.diagram;

    let body = group.findObject('BODY');

    let desiredSize = undefined;
    let newBounds = diagram.computePartsBounds(group.memberParts);

    if (group.isSubGraphExpanded) {

        newBounds.addMargin(new go.Margin(5, 10, 5, 10));
        newBounds.unionPoint(group.location);

        desiredSize = newBounds.size;
    } else {

        desiredSize = new go.Size(NaN, group.actualBounds.size.height);
    }

    diagram.startTransaction('setSize');
    body.desiredSize = desiredSize;
    diagram.commitTransaction('setSize');
};

Now, when I group a selection, I want the group to initially be collapsed. I added the subGraphExpanderButton… Whenever the button is pressed, the SubGraphExpanded or SubGraphCollapsed events are raised… In the SubGraphExpanded event,I first call doLayout on the groups layout in order to position the nodes before calcuating the groups size, which I do afterwards…

Now, the first time the button is pressed, some of the nodes fall out of the group… on each of the next clicks on the same button, the elements of the group are appropriately inside the group…

Do you have any idea why this happens only on the initial expand?

Here are the layout settings for the group:

  layout: $(go.LayeredDigraphLayout, {
                isRealtime: false,
                isOngoing: false,
                layeringOption: go.LayeredDigraphLayout.LayerLongestPathSource,
                columnSpacing: 5,
                setsPortSpots: false
            })

Are the nodes only a little bit outside of the group’s area after expanding the first time? If so, I would guess that the nodes don’t have their intended sizes yet when you compute their bounds, and that’s because they are not visible while the group is collapsed. If that’s the case I will have to think about the best course of action. It might be best to start off expanded and then collapse it.

Hi walter, this is the result of when the group is initially expanded:

I thought that It could be that the location of the group is different from the location of the nodes that are members of the group, but I call the move method which recursively moves all of the group members…
To the best of my knowledge, another possible reason might be a wrong computation of the bound of all of the parts…

So this is what I do:

  • create a group
  • manually add ports and some nodes
  • move the group to an average position of previously selected nodes
  • calculate the new size

P.S. Whenever I press on the expander button to collapse the subgraph, I get the following warning:

Change not within a transaction: !d points: Link#3097([object Object])  old: List(Point)#33844  new: List(Point)#33955

The button is taken from the samples site.

Could you debug your situation by checking on the actualBounds of the newly created Nodes that are members of the newly created Group?

I’m guessing that the Group.layout has no effect on the members of the group while it is collapsed, because the nodes and links are all not isVisible(). Try having the Group be expanded initially, and then collapse it.

Note that that wouldn’t be a problem if you are adding the member nodes and links when expanding the first time, because the group will be expanded and the new member nodes and links are supposed to be visible. But I think your description says that you create the groups collapsed with member nodes, and that arbitrarily later when the user expands the group you have the layout problem.

Ok, so the picture in my last reply is the result when the group is initially expanded, that is, when the user selects nodes and the group is created.
This is the console log of the initial call to setGroupSize (my own function):

There doesn’t seem to be anything strange as far as I can tell…

If the group is initially collapsed, then this is the log:

If the user expands the subgraph it will result to this:

This is almost ok, but I don’t understand why the distance between the border on the left and the node on the left i s larger than the distance between the right border and the node (they practically overlap)…
And, again, the problem, as I previously mentioned, it seems to be worse when the group is initially expanded(?!)…

If you need me to tell you again the steps I take when creating a group, I will, just let me know.

OK, this is getting too complicated for me to understand without seeing and understanding all your code. I said before that you could still use Placeholders, but it gets more complicated, as shown in the SwimLanes sample. So I’ve implemented something that still uses Placeholders, and to me this seems simpler than what we were just discussing:

    myDiagram.groupTemplate =
      $(go.Group, "Spot",
        {
          layout: $(go.TreeLayout),
          ungroupable: true,
          computesBoundsAfterDrag: true,  // needed to prevent recomputing Group.placeholder bounds too soon
          isSubGraphExpanded: false,
          resizable: false,  // not resizable when collapsed
          resizeObjectName: "BODY"
        },
        new go.Binding("isSubGraphExpanded").makeTwoWay(),
        new go.Binding("resizable", "isSubGraphExpanded"),
        $(go.Panel, "Auto",
          $(go.Shape, "RoundedRectangle", { stroke: "gray", fill: "transparent", spot1: go.Spot.TopLeft, spot2: go.Spot.BottomRight }),
          $(go.Panel, "Table",
            { name: "BODY", minSize: new go.Size(50, NaN) },
            new go.Binding("desiredSize").makeTwoWay(),
            $(go.RowColumnDefinition, { row: 0, sizing: go.RowColumnDefinition.None }),
            { alignment: go.Spot.Top, stretch: go.GraphObject.Horizontal },
            $(go.Shape, "RoundedRectangle", { columnSpan: 3, fill: "yellow", stroke: null, stretch: go.GraphObject.Fill }),
            $("SubGraphExpanderButton", { margin: 2 }),
            $(go.TextBlock, { column: 1 },
              new go.Binding("text")),
            $(go.Placeholder,
              { row: 2, columnSpan: 3, alignment: go.Spot.TopLeft },
              new go.Binding("padding", "isSubGraphExpanded", function(e) { return e ? new go.Margin(5, 10) : new go.Margin(0); }))
          )
        ),
        // just two fixed ports, but could be generalized
        $(go.Shape,
          {
            width: 7, height: 7,
            portId: "in", alignment: go.Spot.Left,
            toLinkable: true, toSpot: go.Spot.Left
          }),
        $(go.Shape,
          {
            width: 7, height: 7,
            portId: "out", alignment: go.Spot.Right,
            fromLinkable: true, fromSpot: go.Spot.Right
          }),
      );

    // used by the node template, which otherwise doesn't really matter:
    function stayInGroup(part, pt, gridpt) {
      // don't constrain top-level nodes
      var grp = part.containingGroup;
      if (grp === null) return pt;
      // try to stay within the background Shape of the Group
      var back = grp.findObject("BODY");
      if (back === null) return pt;
      // allow dragging a Node out of a Group if the Shift key is down
      //if (part.diagram.lastInput.shift) return pt;
      var p1;
      if (grp.placeholder !== null) {
        p1 = grp.placeholder.getDocumentPoint(go.Spot.TopLeft);
      } else {
        p1 = back.getDocumentPoint(go.Spot.TopLeft);
      }
      var p2 = back.getDocumentPoint(go.Spot.BottomRight);
      var b = part.actualBounds;
      var loc = part.location;
      // find the padding inside the group's placeholder that is around the member parts
      var m = (grp.placeholder !== null ? grp.placeholder.padding : new go.Margin(0));
      // now limit the location appropriately
      var x = Math.max(p1.x + m.left, Math.min(pt.x, p2.x - m.right - b.width - 1)) + (loc.x - b.x);
      var y = Math.max(p1.y + m.top, Math.min(pt.y, p2.y - m.bottom - b.height - 1)) + (loc.y - b.y);
      return new go.Point(x, y);
    }

There are no DiagramEvent listeners. But I did set CommandHandler.archetypeGroupData in order to support interactive grouping.