Implementing a group list layout

Short of creating a custom layout, is there a best way to achieve groups with an ordered list of nodes?

I’ve so far tried using the GridLayout with a wrappingColumn of 1 as well as computeBoundsAfterDrag to allow us to drag the list item to another group.

This worked great, but when dropping a node onto the void/an invalid target, the group stretches to the nodes location before animating back into place with it.

Ideally ONLY the node animated back into place in this situation, with the group maintaining its dimensions. This is seemingly the only thing missing from GridLayout being a perfect list, but it’s also enough of a drawback to prompt this post and potentially a new layout.

Any ideas? Happy to provide more information.

Layouts are just responsible for arranging nodes, so even if you implemented your own layout you’d get the same behavior.

I’m not sure, but you might not be able to use the default layout animation, and have to implement your own animation. I can look into this later today.

I believe that the Regrouping sample, Regrouping Demo , exhibits the behavior that you are talking about.

So I modified the finishDrop function in that sample so that the dropped nodes are immediately moved to the center of the group. That way any animation will start from there and end wherever the node should be, which probably will expand the group’s placeholder.

      function finishDrop(e, grp) {
        let ok = true;
        if (grp !== null) {
          // if we know that the Group.layout will rearrange the new member nodes,
          // let's start them all at the center of the group
          const c = grp.placeholder.getDocumentPoint(go.Spot.Center);  // assumes a Placeholder!
          grp.diagram.selection.each(part => {
            if (part instanceof go.Link) return;
            if (part.containingGroup === grp) return;
            part.position = new go.Point(c.x - part.actualBounds.width/2, c.y - part.actualBounds.height/2);
          ok = grp.addMembers(grp.diagram.selection, true);
        } else {
          ok = e.diagram.commandHandler.addTopLevelParts(e.diagram.selection, true);
        if (!ok) e.diagram.currentTool.doCancel();

Perhaps you could do something similar in your app.

Have only just seen the edit, that is a great idea. I did eventually implement a custom layout that instead manually resizes the pseudo-placeholder to the target size.

I have come into this problem again with another layout - this time regrouping a sub-graph tree layout.

I’ll investigate using your proposed approach to re-group the moved node. This will help with that situation, but won’t address the issue of placeholders reaching out to “catch” a node out in space if it isn’t regrouped, but instead just hasn’t finished moving back to its position.

Ultimately I think the ideal solution to both is similar to Group.computeBoundsAfterDrag. Having something like Group.computeTargetLayoutBounds such that the placeholder only resizes itself to the final positions of the sub-graph when animations have finished.

If you can think of a solution or even another way to think about that problem, I’m all ears!

I’m not sure about what problems there might be, but you could try temporarily changing your “Auto” Panel to be a “Spot” Panel during the animation. You’d need to temporarily set the desiredSize of the Shape.

EDIT: I modified a copy of the Regrouping sample, Regrouping Demo, to do what I suggested:

myDiagram = new go.Diagram("myDiagramDiv",
      "AnimationStarting": e => {
        e.diagram.skipsUndoManager = true;
        e.diagram.nodes.each(g => {  // this is very inefficient
          if (g instanceof go.Group) {
            g.type = go.Panel.Spot;
            const main = g.findMainElement();
            main.desiredSize = main.actualBounds.size;
        e.diagram.skipsUndoManager = false;
      "AnimationFinished": e => {
        e.diagram.skipsUndoManager = true;
        e.diagram.nodes.each(g => {
          if (g instanceof go.Group) {
            g.type = go.Panel.Auto;
            const main = g.findMainElement();
            main.desiredSize = new go.Size(NaN, NaN);
        e.diagram.skipsUndoManager = false;
      . . .

It still isn’t clear to me what animation behavior you wanted. I’m curious if this behavior is closer to what you want. If it isn’t what you want, what do you want to be different?