Node.diagram.selection.count inconsistent behavior

Hello!

I have following requirements for the nodes on a canvas that have annotations:

  • When 1 node is selected, full length annotation is being displayed for that node.
  • When more than 1 node is selected, all the selected nodes should have limited number of lines in that annotation, let’s say 5.

What I’m trying to do is to check the count of the selected nodes via node.diagram.selection.count === 1 and node.isSelected to decide what number of lines to display.

The problem I’m seeing is that when I check node.diagram.selection.count, the behavior is very inconsistent. Sometimes it would act as if one node is selected, sometimes as multiple. That creates all kinds of random results for each node. Most of the times the node that was added the last would have the problem and would display correct annotation length only 50% of the times.

I would appreciate any suggestions!

Could you please show us a sketch or screenshot of what you want when there are multiple nodes selected? Should this annotation be shown as part of one node, and which one? Or should it be shown as an Adornment near one of the nodes?

Please see attached. Let me know if that makes sense, or you need any other details.

Is each node just the rounded rectangle shape with some text inside? Those nodes are not part of any annotation, right? And those lines showing “line n” are the only thing seen as part of each annotation? Or are those annotations part of each node?

Yes, and annotation is a part of each node. Node can have annotation added or removed by double-clicking on each node and having a textBlock opened (annotation is a textBlock).

For a single node:

  • when node is selected - show the whole annotation
  • when node is NOT selected - limit annotation to the certain number of lines

For multiple nodes (selected or not), limit annotation to the certain number of lines, for all the nodes

Bindings are evaluated when the Part (i.e. Node or Link) is added to the Diagram. So if you annotation TextBlock has a Binding on its “text” property and you are using a binding conversion function to compute the text you want to show, the values pertaining to the node itself will be up-to-date, but values relating to the rest of the diagram will not be. In fact, depending on values such as diagram.nodes.count will vary as each node is created and added to the diagram.

So it seems to me that if you want the annotation of each node to depend on the state of the whole diagram, you will have to update all of them when you want to. Data binding works for keeping property state up-to-date locally for each Part – it doesn’t work for global state. There is an exception: Binding.ofModel, depending on data properties of the shared object Model.modelData.

So I’m not sure when you want to update all of the nodes. It seems that you want to do so in a “ChangedSelection” DiagramEvent listener, and maybe after some nodes have been added or removed from the diagram/model.

Here’s an example app showing some of the issues:

<!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:400px"></div>
  <textarea id="mySavedModel" style="width:100%;height:250px"></textarea>

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

const myDiagram =
  $(go.Diagram, "myDiagramDiv",
    {
      "InitialLayoutCompleted": e => updateAllAnnotations(),
      "ChangedSelection": e => updateAllAnnotations(),
      "ModelChanged": e => {
        if (e.change === go.ChangedEvent.Insert || e.change === go.ChangedEvent) {
          updateAllAnnotations();
        }
        if (e.isTransactionFinished) {  // show the model data in the page's TextArea
          document.getElementById("mySavedModel").textContent = e.model.toJson();
        }
      },
      "undoManager.isEnabled": true
    });

myDiagram.nodeTemplate =
  $(go.Node, "Vertical",
    // maybe you'll want this setting to prevent a new layout whenever an annotation
    // change causes the node to change size?
    //??? { layoutConditions: go.Part.LayoutStandard & ~go.Part.LayoutNodeSized },
    $(go.Panel, "Auto",
      $(go.Shape, "RoundedRectangle",
        { fill: "white" },
        new go.Binding("fill", "color")),
      $(go.TextBlock,
        { margin: 8 },
        new go.Binding("text"))
    ),
    $(go.TextBlock,
      { name: "ANNOT", textAlign: "center" },
      new go.Binding("text", "", computeAnnotation)),
    {
      doubleClick: (e, node) => {  // toggle the visibility of the annotation
        e.diagram.commit(d => {
          const annot = node.findObject("ANNOT");
          if (annot) annot.visible = !annot.visible;
        });
      }
    }
  );

function computeAnnotation(data, textblock) {
  const node = textblock.part;
  return data.text + "\n" +
         `isSelected: ${node.isSelected}\n` +
         `# selected: ${node.diagram.selection.count} of ${node.diagram.nodes.count}`;
}

function updateAllAnnotations() {
  myDiagram.commit(d => d.updateAllTargetBindings());
}

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" }
]);
  </script>
</body>
</html>

Adding binding to the annotation limit made it work! :)
Thank you for your help!