Collapse/Expand all groups

Hi,
I am trying to expand/colapse all the groups in the graph.
Below is the code for doing that

   this.graphComponent?.graphDiagram.commit(d => {
      d?.model?.nodeDataArray?.forEach(nodeData => {
        const nodeObject = d.findNodeForKey(nodeData?.id);
        if (
          nodeObject &&
          nodeObject?.part.data.isGroup &&
          nodeObject?.part.data.disabled !== true
        ) {
          if (this.expandAll?.value) {
            (nodeObject.part as go.Group).expandSubGraph();
          } else {
            (nodeObject.part as go.Group).collapseSubGraph();
          }
        }
      });
    });

The issue is, the first time I expand the groups the animation starts from somewhere else (The expanded group fades in/slides somewhere from top to the required position)
From the second time the animation looks fine, it starts from the nearby place (Almost similar to the animation when you click on the expand button)
What might be the issue?

This is what I would try:

myDiagram.commit(diag => diag.findTopLevelGroups().each(g => g.isSubGraphExpanded = false));

or:

myDiagram.commit(diag => diag.nodes.each(g => {
  if (g instanceof go.Group) g.isSubGraphExpanded = true;
}));

Tried above, still the same behavior. The first time the animation still starts from the top of that graph.
I mean, all the groups that are at the bottom also have the animation starting at the top of the graph.

In the next expand/collapse tries animation looks fine.

ezgif.com-gif-maker

Does your diagram start off with some or all groups collapsed?

The diagram starts off with all groups collapsed.

I’ll try some experiments today.

Could you tell us more about the layouts and what happens when a group is expanded, especially for the first time? I’m unable to reproduce any problem. Here’s the code I’m using:

<!DOCTYPE html>
<html>
<head>
  <title>Minimal GoJS Sample</title>
  <!-- Copyright 1998-2022 by Northwoods Software Corporation. -->
</head>
<body>
  <div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>

  <script src="go.js"></script>
  <script id="code">
const $ = go.GraphObject.make;

myDiagram =
  $(go.Diagram, "myDiagramDiv",
    {
      //layout: $(go.GridLayout, { alignment: go.GridLayout.Position, cellSize: new go.Size(1, 1) }),
      layout: $(go.TreeLayout, { angle: 90 }),
      "undoManager.isEnabled": true
    });

myDiagram.nodeTemplate =
  $(go.Node, "Auto",
    $(go.Shape, { fill: "white" }),
    $(go.TextBlock,
      { margin: 8 },
      new go.Binding("text", "key"))
  );

myDiagram.groupTemplate =
  $(go.Group, "Vertical",
    {
      //layout: $(go.GridLayout, { alignment: go.GridLayout.Position, cellSize: new go.Size(1, 1) }),
      layout: $(go.TreeLayout, { angle: 90 }),
      isSubGraphExpanded: false
    },
    $(go.Panel, "Horizontal",
      $("SubGraphExpanderButton"),
      $(go.TextBlock, { font: "bold 12pt sans-serif" },
        new go.Binding("text", "key"))
    ),
    $(go.Panel, "Auto",
      $(go.Shape, { fill: "#00000010" }),
      $(go.Placeholder, { padding: 5 })
    ),
  );

const nda = [];
const lda = [];
function addGroup(grp, numgrps, numnds, level) {
  let first = null;
  for (let i = 0; i < Math.max(numnds, 1); i++) {
    const nd = { key: nda.length };
    if (grp) nd.group = grp.key;
    nda.push(nd);
    if (!first) {
      first = nd;
    } else {
      lda.push({ from: first.key, to: nd.key });
    }
  }
  for (let i = 0; i < numgrps; i++) {
    const gd = { key: nda.length, isGroup: true };
    if (grp) gd.group = grp.key;
    nda.push(gd);
    if (level > 0) addGroup(gd, level > 1 ? Math.random()*4 : 0, Math.random()*4, level-1);
    lda.push({ from: first.key, to: gd.key });
  }
}
addGroup(null, 4, 4, 3);
myDiagram.model = new go.GraphLinksModel(nda, lda);
  </script>
</body>
</html>

Below is the layout code

This is the group template

   const go = this.graphComponent.goObject;
    const $ = go.GraphObject.make;
    const updateGroupLabel = g => {
      const tb = g.findObject("LABEL");
      if (!tb) return;
      tb.text =
        (g.isSubGraphExpanded ? "-" : "+") +
        (g.part.data?.metaData?.nodeCount ||
        g.part.data?.metaData?.nodeCount === 0
          ? g.part.data.metaData.nodeCount
          : g.memberParts.filter(m => m instanceof go.Node).count);
    };
    const customGroup = $(
      go.Group,
      "Vertical",
      {
        locationSpot: go.Spot.Center,
        layout: $(go.LayeredDigraphLayout, {
          direction: 90,
          packOption: go.LayeredDigraphLayout.PackStraighten,
        }),
        subGraphExpandedChanged: updateGroupLabel,
        memberAdded: updateGroupLabel,
        memberRemoved: updateGroupLabel,
        selectable: false,
      },
      new go.Binding("isSubGraphExpanded", "", function(data) {
        if (data.disabled) {
          return false;
        }
        return data.isSubGraphExpanded;
      }).makeTwoWay(function(expandState, data, model) {
        model.setDataProperty(data, "isSubGraphExpanded", expandState);
      }), // remember expansion state
      $(
        go.Panel,
        "Auto",
        {
          click: (e, g) => {
            if ((g.part as go.Group).data.disabled !== true) {
              if ((g.part as go.Group).isSubGraphExpanded) {
                e.diagram.commandHandler.collapseSubGraph(g.part as go.Group);
              } else {
                e.diagram.commandHandler.expandSubGraph(g.part as go.Group);
              }
            }
          },
        },
        new go.Binding("cursor", "disabled", function(v) {
          return v ? null : "pointer";
        }),
        new go.Binding("toolTip", "", (data: any) => {
          return data?.disabled
            ? this.createToolTipTemplate(data?.metaData, $)
            : null;
        }),
        $(
          go.Shape,
          "RoundedRectangle",
          {
            fill: "#FFF",
            strokeWidth: 1,
          },
          new go.Binding("stroke", "disabled", function(v) {
            return v ? "#B4B3BD" : "#D6E2FA";
          })
        ),
        $(
          go.TextBlock,
          {
            name: "LABEL",
            margin: new go.Margin(6, 8, 6, 8),
            font: "bold 12pt open sans",
          },
          new go.Binding("stroke", "disabled", function(v) {
            return v ? "#B4B3BD" : "#3470E4";
          })
        )
      ),
      $(
        go.Panel,
        "Vertical",
        new go.Binding("visible", "isSubGraphExpanded").ofObject(),
        $(
          go.Shape,
          "LineV",
          { width: 2, height: 7 },
          new go.Binding(
            "stroke",
            "metaData",
            metaData => metaData?.strokeColor
          )
        ),
        $(
          go.Shape,
          {
            toArrow: "Triangle",
            angle: 90,
            stroke: null,
            strokeWidth: 0,
          },
          new go.Binding("fill", "metaData", metaData => metaData?.strokeColor)
        ),
        $(
          go.Panel,
          "Auto",
          $(go.Shape, "RoundedRectangle", {
            fill: "white",
            strokeWidth: 1.5,
            strokeDashArray: [2, 2],
          }),
          $(go.Panel, "Vertical", $(go.Placeholder, { padding: 10 }))
        )
      )
    );
    const templateMap = new go.Map<string, go.Group>();
    templateMap.add("additional_node_group", customGroup);
    templateMap.add("", this.graphComponent.graphDiagram.groupTemplate);
    this.graphComponent.graphDiagram.groupTemplateMap = templateMap;

To expand/collapase all groups I use this code

   this.graphComponent?.graphDiagram.commit(d => {
      d?.model?.nodeDataArray?.forEach(nodeData => {
        const nodeObject = d.findNodeForKey(nodeData?.id);
        if (
          nodeObject &&
          nodeObject?.part.data.isGroup &&
          nodeObject?.part.data.disabled !== true
        ) {
          if (this.expandAll?.value) {
            (nodeObject.part as go.Group).expandSubGraph();
          } else {
            (nodeObject.part as go.Group).collapseSubGraph();
          }
        }
      });
    });

I still don’t see what’s going on. I think the layout details don’t matter. But maybe the node template does matter. What property settings and bindings does the node template have with respect to position or location?

BTW, you could replace:

$(go.Panel, "Vertical", $(go.Placeholder, { padding: 10 }))

with:

$(go.Placeholder, { padding: 10 })