Show/Hide groups without hiding child nodes

Hi, I’ve got a diagram that has nested groups and a button/feature to show or hide certain types of groups.

Pressing the button should hide the group without affecting (hiding or moving) the child nodes.

Previously when using the ReactDiagram component this was trivial because the data was re-written every time and the diagram would update accordingly.
Now using GoJS manually it is not working as expected.

Hiding the group and not hiding/moving the children is easy and works well.
However, the child nodes don’t jump back into their parent group when Unhiding the group that has previously been hidden.

Screenshot #1:
Here is one group with three children, one is another group with itself one child.
before

Screenshot #2:
Here everything is after clicking the “hide group” button. We see the two parent groups have been hidden and the non-group children remain.
after

Screenshot #3:
And here is the issue, after clicking the “hide group” button again, the parent groups ARE added, but their children nodes are not inside them.

Any ideas?
Thanks as always.

What does that button do?

Does the Group template include a Placeholder?

I don’t see a placeholder in the group template.

const groupTemplate = $(
    go.Group,
    "Auto",
    {
      layout: $(go.TreeLayout, {
        angle: 90,
        arrangement: go.TreeLayout.ArrangementHorizontal,
      }),
      isAnimated: true,
      isSubGraphExpanded: true,
      selectionAdorned: false,
    },
    new go.Binding("visible", "isHidden", (isHidden) => !isHidden),
    new go.Binding("isSubGraphExpanded", "", () => false),
    $(
      go.Shape,
      // surrounds everything
      "RoundedRectangle",
      {
        parameter1: 5,
        name: "NODE_BODY",
      },
// Lots of stuff in here: panels, textboxes, etc...
);

As for what the button does, it hides the specified group and changes the child “group” property to an empty string.

// Hide Parent
if (node.source === "SpecifiedGroup" && !node.isHidden) {
      node.isHidden = true;
    }
// Remove Child from parent group
    if (
      !node.isGroup &&
      !node.isHidden &&
      node.group &&
      node.group.startsWith("SpecifiedGroup")
    ) {
      // save the value for later
      node.__originalGroup = node.group;
      node.group = "";
    }

If the Group does not have a Placeholder, what determines the position and size of the group?

In the button click code, is node a node data object that is in the model? If so, you need to call model methods when you modify data. For example, call Model.set when setting any data property.

Hmmm, yes true, I didn’t give enough context.

The node items are node data items from the model.
At the end of the function (I didn’t include it in the snippet, sorry) set a property (node.__shouldUpdate) to true. This allows us to programmatically update only the nodes that need updating in a transaction.

The transaction code looks like this:
Basically, if the node should be updated and its group has been set to “” remove it from the group.

diagram.commit((d: Diagram) => {
    d.nodes.each((node) => {
      if (node?.data?.__shouldUpdate) {
        const data = node?.data;
        if (data.group === "") {
          d.model.setDataProperty(data, "group", undefined);
        }
        // Update the asset's data bindings
        node.updateTargetBindings();
      }
    });

I did some debugging last night, and I have a suspicion that the code is doing exactly what it should.
And the reason why the child nodes don’t magically reappear in their parent groups after toggling the “SpecifiedGroup” off and then on again is that they still exist in their original spot. (it is hard to tell from the screenshots but the child nodes never move or change visually, just the groups that contain them)

I may not understand all of your requirements, but my first impression is that it would be easiest if you put all of the groups that you expect to show/hide into a separate Layer. That way you can just set that layer’s Layer.visible or Layer.opacity property in order to show/hide all of those groups.

The advantage of such a scheme, or even just iterating through all of the groups (Diagram.nodes), is that it doesn’t modify the model, which I assume you would like to maintain whole no matter what you show to the user.

Hmm, I never thought of using layers for different groups, interesting.
You are correct about us not wanting to change the model (unless needed)

Question, if a group (with children) is in a layer and we change the Layer.visible or Layer.opacity do the children remain visible?
I guess my question is: are the child nodes in a group placed on the same layer as the group?

Each Part has its own Part.layerName property, and thus Part.layer value. (If a layer name doesn’t exist, it will be put into a different layer, by default the layer whose name is the empty string, “”.)

So if you have enough Layers, you could have each Node and each Group be in a different Layer. That would be an inefficient way to implement things, though. Usually the pre-defined three layers, “Background”, “”, and “Foreground” are sufficient for most purposes. GoJS Layers -- Northwoods Software

Good to know.
I just tested by placing “SpecifiedGroup” on its own layer behind the default layer.
Then showing/hiding the layer.

The children nodes are on the default layer but they disappear as well when hiding the layer that the groups are on.

Adding a layer to diagram:

const background= diagram.findLayer("Background");
diagram.addLayerBefore($(go.Layer, { name: "blue" }), background);

Group template:

$(
  go.Group,
  "Auto",
  {
    layout: GO_DIAGRAM(go.TreeLayout, 
  },
  layerName: "blue",
);

Example onClick:

diagram.commit(d => {
    var layer = d.findLayer("blue");
    if (layer !== null) layer.visible = !layer.visible;
  }, 'toggle ' + "blue");

The child nodes say they are on layerName “” (the default)
Am I doing something wrong, or it’s normal that the child nodes disappear when hiding their containing group even if the group is on a different level?

Yes, visible is “inherited” by member Parts and Panel elements.

Try setting opacity and pickable instead.

Thanks, that works very well and seems pretty lightweight.

The behaviour of the links is interesting when using layers.
When hiding the layer with the “SpecifiedGroup” links that connect a node on the default layer to a group on the “blue” layer are only half hidden.

halfhidden

See the second blue link coming from the node at the bottom, it goes halfway to where the group (which is hidden on the “blue” layer)

Ah, it is not compatible with our situation, unfortunately.
We allow the users to expand and collapse all groups (if there are any) at any time.
This becomes complicated very fast with groups that exist but are invisible with child nodes inside them.

In any case, I will keep this layer technique in mind for other things it is very slick.

Let’s say I wanted to create a function that starts an animation or something similar.
The function would expand the “SpecifiedGroup” that has just been Unhidden.
Then go find all of its children in the diagram model.
Then move them physically into the container group.

How possible is that?

That sounds straight-forward to implement. I hope you have read GoJS Animation -- Northwoods Software. Did you have a specific question?

Yes, sorry the question was not clear.
When a node is rendered physically outside of its parent group. (like my third screenshot at the start of this thread)
How do I animate it moving into its parent groups?

Did you want the group to (animatedly) expand to include the missing node, or did you want to (animatedly) move the node to be where the group is?

Do you know how to set up a simple Animation, say for moving a node? GoJS Animation -- Northwoods Software

Yes, if the group could be programmatically opened and the nodes animate or move into the group that would be ideal.

A second option could be to leave the group collapsed and have the nodes move toward the group and disappear (like the Animating Deletion example)
Of course, the nodes would have to be in the group when the user clicks the expand button.

I have tried a few simple animations (translate, rotate, disappear) but didn’t see anything related to animating (or moving) nodes into or out of groups.

Here’s a complete example of animating the location of a node:

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

  <script src="https://unpkg.com/gojs"></script>
  <script id="code">
const $ = go.GraphObject.make;

const myDiagram =
  $(go.Diagram, "myDiagramDiv",
    { "undoManager.isEnabled": true });

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

myDiagram.model = new go.GraphLinksModel(
[
  { key: 1, text: "Alpha", color: "lightblue" },
  { key: 2, text: "Beta", color: "orange" },
  { key: 3, text: "Gamma", color: "lightgreen" },
  { key: 4, text: "Delta", color: "pink" }
],
[
  { from: 1, to: 2 },
  { from: 1, to: 3 },
  { from: 2, to: 2 },
  { from: 3, to: 4 },
  { from: 4, to: 1 }
]);

document.getElementById("myTestButton").addEventListener("click", e => {
    const node = myDiagram.nodes.first();
    const an = new go.Animation();
    an.add(node, "location", node.location, new go.Point(node.location.x + Math.random()*100-50, node.location.y + Math.random()*100-50));
    an.start();
  });
  </script>
</body>
</html>