Re-arrange the internals of a group

Hi,
I have groups which have nodes inside of them that the user can drag and interact with. What I want is that once the user has done interacting with a node in that group, the internals of the group rearrange themselves back into place. I can bind it to some click event, that is not a problem, but I really am not sure how can I trigger a layout just for that group. Thanks

Call Layout.invalidateLayout on the group’s Group.layout.
The layout will be performed by the end of the current transaction.

So I will be attaching this even to double-clicking on a node inside the group. From what I am thinking, I will have to find the parent group of that node inside the double click function, and then do a parent.layout.invalidateLayout. Is that what you mean?

Just call Part.invalidateLayout on the member Node: Part | GoJS API

If Part.canInvalidateLayout is true, it finds the responsible Group.layout or Diagram.layout and calls Layout.invalidateLayout on that.

So I have tried what you suggested in my scenario, but it wasn’t working for some reason. So I did some testing and debugging, and have pointed the reason why it wasn’t working in my case. The reason is that the group templates in my client side have no layouts, but the children nodes are positioned according to the location property contained in their data objects inside the our nodeDataArray. What can I do to get the nodes inside a specific container to be rearranged according to their locations inside the provided nodeDataArray, without re-rendering the entire diagram?

Additionally, I have noticed that calling Layout.invalidateLayout on a node will perform layouts on the node’s parent group, as well as on subsequent groups in the diagram having the same template as that node’s parent. I don’t want that, I just want the clicked node’s parent group to be re-rendered according to the data in the nodeData array

Ah, yes, if there is no layout for a group, then Group.layout will be null, and of course one cannot do anything with no Layout. In this case Part.invalidateLayout will keep going up the chain of containing Groups until it finds a Group.layout, or until it reaches the Diagram at which point it uses the Diagram.layout which is guaranteed to be non-null.

Note that I was suggesting calling Node.invalidateLayout, not Layout.invalidateLayout which does not take a Node as an argument.

I think instead of calling Part.invalidateLayout or Layout.invalidateLayout you should just call Part.updateTargetBindings on all of the Group’s member Nodes.
So if your node template has a binding such as:

  new go.Binding("location", "loc", go.Point.parse),

Then maybe something like:

function restoreSavedLocations(group) {
  group.diagram.commit(diag => {
    group.memberParts.each(member => {
      if (member instanceof go.Node) member.updateTargetBindings("loc");
    });
  });
}

Substitute the actual name of your data property that has the location information, instead of “loc”.

Of course this will not work if you do have a TwoWay Binding on the Node.location property. If you did, then any movement of a Node, whether by the DraggingTool as the user moves a node or by a Layout, would save the latest location in the node data in the model, so re-evaluating that Binding would have no effect because it would be setting the location to its current value.

Thanks, this seems to do the job of rearranging the nodes, but for some reason it is not doing that correctly. The problem is quite bizarre tbh. So for context, I am not using any layouts, but the nodes and groups position themselves according to the location that is given to them, by using location binding. So I have console logged the event.subject.first() on diagram.SelectionChanged event, and here is what is happening upon inspecting the nodes that are printed on the console:

  • The location of the nodes in the node.data object, and the location in the event.subject.first().location object (which I believe represents the location of the node in the diagram) is completely different, despite the location bindings.
  • Upon calling the updateTargetBindings(“loc”) function on a given node, it gets pushed to a completely different position to both the locations in the node.data object, and the location which was previously in its event.subject.first().location object

I am firstly not sure why the location binding is not working, and secondly why the updateTargetBindings function give the node a completely new and random position?

Edit: I think I see a pattern to the location the node assumes on updating location binding. So the nodes always seem to go to the top left of their root container. So for a node which is third-level nested (rootContainer → parentContainer → theNode), the parentContainer is stretched all the way up to the top-left of the root container, which is where these nodes appear to go to.

That probably depends a lot on exactly how you have implemented your group template.

I don’t understand. None of my group templates have any layouts, just one way location bindings. The node template also has oneWay location bindings. What then could be the reason for different locations in the data object and the location object?

OK, that’s good to know.

Does the template have a Placeholder? Are Groups collapsible?

Actually yes. All group templates have placeholders, and can be expanded/collapsed. The root container actually starts off collapsed, and only upon expanding it are rest of the nodes made visible. But I am assuming that this expand/collapse ability will have no bearing on location bindings, and that once expanded, every node will assume a relevant position as dictated by the location in the data object.

Well, the Placeholder is what determines the location and size of the group, when the group is expanded. If you really want the location and size of the group to be determined independently of its member nodes, you should not use a Placeholder.

Okay so removing the placeholder does solve this problem, but now my diagram is all messed up, with the headers for groups now floating in the middle of their children nodes in space. The background for the groups is gone as well. What really baffles me here is that other than the background losing existence and the headers for the groups floating casually in space, the positioning of nodes now being the same as the loc property in the data object is still giving the exact same appearance and layout overall. My nodes were previously arranged neatly in a grid like manner and they still are, but I don’t understand that if previously, with placeholders, they were not following the location instructions provided in the data object, how then were they visually positioned the same as when they are positioned according to the location in their data object (without placeholders)?

What did you replace the Placeholder with?

Please read: GoJS Sized Groups -- Northwoods Software

Thanks for sharing the article. I have replaced it with a shape now, which seems to be doing fine. However I have left the nodes draggable, and with placeholders, the group’s boundaries expand if you try to drag a node out of the group’s premises. Is it possible to achieve the same effect using shapes instead of placeholders?

Sure – either implement a “SelectionMoved” DiagramEvent listener, or customize the DraggingTool. Depends on what you want to do. There are a bunch of examples of various things. Caution: there are a lot of choices that are reasonable to make – so it might be more complicated than you initially think.