Bind adornment visibility to model

I wanna define a custom adornment, which is not a tooltip nor a context menu nor a selection. I want this to be bound to the model data so if data.blah is set to true it is shown.

I haven not been able to find a declarative way of defining this adornment on the node template. So what I have is this:

function labelAdornment() {
  return new go.Adornment("Spot", {
    name: "label_adornment",
  })
    .add(
      new go.Placeholder({
        isActionable: true,
      })
    )
    .add(
      new go.Shape("RoundedRectangle", {
        alignment: go.Spot.TopLeft,
        alignmentFocus: go.Spot.BottomLeft,
      })
    );
}

And then I turn this on this way:

nodeTemplate = new go.Node(go.Panel.Auto, {
  resizable: true,
  resizeObjectName: "box",
})
  .add(
    new go.Panel(go.Panel.Auto, {
      alignment: go.Spot.TopLeft,
    })
      .add(
        new go.Shape("RoundedRectangle", {
          fill: "ghostwhite",
          stroke: "lightgray",
        })
      )
  )
  .bind(
    new go.Binding("visible", "", (data, obj: go.GraphObject) => {
      if (data.blah) {
        const adornment = labelAdornment();
        adornment.adornedObject = obj?.part;
        obj?.part?.addAdornment("label", adornment);
      }
      return true;
    })
  );

But this looks like a hack and I don’t like it. What’s a better way of doing this?

Tooltips and context menus assume there is only one tooltip or context menu shown at a time, whether or not they are implemented in GoJS or in HTML. It sounds as if that is not what you want, because at the same time you could have any number of nodes showing different instances of the desired Adornment. So you can ignore any discussion about tooltips or context menus or HTMLInfos.

And you want to avoid the “Selection” Adornment mechanism because you don’t want to depend on selection to show the Adornment.

If you want to manage your own Adornments, calling Part.findAdornment, addAdornment, and removeAdornment make sense. For efficiency, you probably want to always try to update an Adornment if it exists, rather than always creating a new one and maybe throwing away an existing one, if you can.

Is there always at most one of your Adornments that might be shown for a Node at any one time? In other words, not two of them for the same Node at the same time?

For simple appearance or behavior changes you could use the Part.isHighlighted property bound to your “blah” data property, and use Binding.ofObject Bindings to modify the properties of GraphObjects in your Node template. No Adornments needed. Instead this uses a boolean property on Parts and the Diagram.highlighteds collection and some methods on Diagram. Data Binding | GoJS

However, if what you want to show isn’t a simple property change to existing GraphObjects in the visual tree of your Nodes, probably because what you want to show extra is complex and not something you want every Node to have in them, then using separate Adornments instead of ofObject Bindings makes sense.

One way to do that is to depend on Part.isHighlighted, as above, but to programmatically add and remove Adornments as needed. Maybe you could implement a Part.highlightedChanged event handler that does what you want. Perhaps something like this, which just shows a simple arrow pointing at the node, or not:

    // show or hide an arrow shape for the "proband" node
    function highlightedChanged(node) {
      if (node.isHighlighted) {
        const ad =
          new go.Adornment("Spot")
            .add(
              new go.Placeholder(),
              new go.Shape({
                  alignment: go.Spot.BottomLeft, alignmentFocus: go.Spot.TopRight,
                  strokeWidth: 2, scale: 2,
                  geometryString: "F1 M20 0 L14.5 5.5 12 1z M18 1 L0 10"
                })
            );
        ad.adornedObject = node.findObject("ICON") || node;
        node.addAdornment("Highlight", ad);
      } else {
        node.removeAdornment("Highlight");
      }
    }

Hmmm, this example doesn’t try to reuse Adornments – it just blindly creates and deletes Adornments as the value of Node.isHighlighted changes.