Adornments on diagram load

Hi Team,
Our use case: We want a set of adornments to be visible on nodes on diagram load and stay, then we control the adornments using the visible key which adornments to be visible and which to hide. The arrows are the adornments in the below image.

From what i gathered from other topics like Permanent adornment with indicators ,
It seems like making them part nodetemplate instead of using adorments and using the selectionObjectName is the way to go.

Please let me know if there is a better way to achieve this using adornments? Also please mention any limitations of using selectionObjectName to avoid unexpected behaviour.

Yes, there is a wide range of implementation possibilities when there are so many possible behaviors.

If you want some number of decorations to really be a part of the node, then putting them into the node template makes the most sense.

If you want nodes to fundamentally be a particular size, ignoring any possible decorations, then using Adornments to implement those decorations makes sense.

Did you want to have (say) five different types of decorations, and then did you want to control the visibility of all Adornments corresponding to a particular type of decoration for all Nodes at the same time? Or did you want to control which decorations are visible individually for each node, with perhaps many nodes all showing some desired different subsets of decorations?

In the former case, is there basically a global variable/flag controlling the visibility for each of the types of decorations that you might show?

In the latter case, is there a data property on each node controlling the visibility of each type of decoration for just that node?

Hi @walter , Below is our exact use case.
The arrow like adornments are supposed to be drop targets for other nodes. Meaning, whenever i drag a node all the other nodes should show possible drop targets around them, In the below example, when the node is being dragged condition (1) shows right and left adornments meaning the node can be moved to the left of it or to the right. When dropped the dropped node will be moved based on the drop target. Below are the screenshots.

When dragging started:

When dragged to a drop target:

The approach we thought was to have adornments to all nodes when the diagram initially loads and then control visibility of them based on dragging events by looping through

When dropped:

The approach we thought of was to have adornments attached to each node on the diagram load itself and use property visible to show or hide adornments of each node whenever a node is being dragged.

But from previous posts it seems like making them as GraphObjects might be a better option?
Please let know how we can implement our use case and which is the better way to go using Adornments or GraphObjects for our use case?

This seems a quite different requirement than the subject of this topic and your initial description. Originally this was about what could be visible when the diagram is loaded; now you are asking about making things visible only during a drag.

I can create a sample for you later today when I have some free time.

Did you want to show all of those Adornments when the DraggingTool starts, or when the mouse/finger is close, or some other criteria?

Is your graph always tree-structured?

I think this sample demonstrates what you are asking for.

Note that the details of the Node and Link templates don’t matter at all, although I did add a mouseDrop event handler to the Node template to handle the case when the user drops onto the Node instead of onto the “Drop” Adornment.

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

  <script src="https://cdn.jsdelivr.net/npm/gojs/release/go-debug.js"></script>
  <script id="code">
class CustomDraggingTool extends go.DraggingTool {
  constructor(init) {
    super();
    if (init) Object.assign(this, init);
  }

  doActivate() {
    super.doActivate();
    const primary = this.diagram.selection.first();
    if (primary instanceof go.Node) {
      this.diagram.nodes.each(n => {
        if (n === primary || n.isTreeRoot ||
            (this.draggedParts && this.draggedParts.has(n)) ||
            (this.copiedParts && this.copiedParts.has(n))) return;
        const ad = DropAdornmentTemplate.copy();
        ad.adornedObject = n;
        n.addAdornment("Drop", ad);
      });
    }
  }

  // also invoke mouseDrag... and mouseDrop event handlers on Adornments;
  // normally this ignores all temporary Parts
  findDragOverObject(pt) {
    return this.diagram.findObjectAt(pt, null,
      o => !(this.draggedParts && this.draggedParts.has(o.part)) &&
           !(this.copiedParts && this.copiedParts.has(o.part))
    );
  }

  doDeactivate() {
    this.diagram.nodes.each(n => n.removeAdornment("Drop"));
    super.doDeactivate();
  }
}

const myDiagram =
  new go.Diagram("myDiagramDiv", {
      layout: new go.TreeLayout({
          angle: 90,
          arrangement: go.TreeArrangement.Horizontal,
          sorting: go.TreeSorting.Ascending,
          comparer: (va, vb) => {
            const da = va.node.data;
            const db = vb.node.data;
            return da.index - db.index;
          }
        }),
      draggingTool: new CustomDraggingTool({ dragsTree: true }),
      // disallow drop onto the diagram background
      mouseDrop: e => e.diagram.currentTool.doCancel(),
      "undoManager.isEnabled": true,
      "ModelChanged": e => {     // just for demonstration purposes,
        if (e.isTransactionFinished) {  // show the model data in the page's TextArea
          document.getElementById("mySavedModel").textContent = e.model.toJson();
        }
      }
    });

// the details of the node template really don't matter
myDiagram.nodeTemplate =
  new go.Node("Auto", {
      // what does it mean to drop a node onto a node?
      mouseDrop: (e, node) => {
        if (node.isTreeRoot) {  // disallow drop onto a root node
          e.diagram.currentTool.doCancel();
        } else {
          dropOnto(node, true);
        }
      }
    })
    .add(
      new go.Shape({ fill: "white" })
        .bind("fill", "color"),
      new go.TextBlock({ margin: 8 })
        .bind("text"),
      new go.TextBlock({ font: "8pt sans-serif", alignment: go.Spot.BottomRight, margin: 1 })
        .bind("text", "index")
    );

// this must be a template because there will be many copies, one per node
const DropAdornmentTemplate =
  new go.Adornment("Spot")
    .add(
      new go.Placeholder(),
      new go.Shape("TriangleLeft", {
          alignment: go.Spot.Left, alignmentFocus: new go.Spot(1, 0.5, 1, 0),
          width: 12, height: 18,
          fill: "orange", strokeWidth: 0,
          mouseDragEnter: (e, button) => button.fill = "green",
          mouseDragLeave: (e, button) => button.fill = "orange",
          mouseDrop: (e, button) => dropOnto(button.part.adornedPart, false)
        }),
      new go.Shape("TriangleRight", {
          alignment: go.Spot.Right, alignmentFocus: new go.Spot(0, 0.5, -1, 0),
          width: 12, height: 18,
          fill: "orange", strokeWidth: 0,
          mouseDragEnter: (e, button) => button.fill = "green",
          mouseDragLeave: (e, button) => button.fill = "orange",
          mouseDrop: (e, button) => dropOnto(button.part.adornedPart, true)
        })
    ).copyTemplate(false);

// each selected Node will become a sibling of the ONTO node,
// either just before or just after ONTO in its list of siblings
function dropOnto(onto, after) {
  if (!onto || onto.isSelected) return;
  const diagram = onto.diagram;
  const parent = onto.findTreeParentNode();  // might be null
  diagram.commit(() => {
    diagram.selection.each(part => {
      if (part instanceof go.Node) {
        const parentlink = part.findTreeParentLink();
        if (parentlink !== null) {  // remove from any parent
          parentlink.diagram.remove(parentlink);
        }
        if (parent !== null) {  // add to new parent and assign temporary index
          // this assumes the model is a TreeModel (otherwise add a link data object to GraphLinksModel)
          diagram.model.setParentKeyForNodeData(part.data, parent.key);
          diagram.model.set(part.data, "index", onto.data.index + (after ? 0.5 : -0.5));
        }
      }
    });
    if (parent !== null) {  // sort the children and assign permanent index values
      const children = new go.List(parent.findTreeChildrenNodes());
      children.sort((a, b) => a.data.index - b.data.index);
      let i = 0;
      children.each(c => diagram.model.set(c.data, "index", i++));
    }
    // now the TreeLayout will sort each parent's children properly, based on the data.index value
  });
}

function makeTree(level, count, max, maxChildren, nodeDataArray, parentdata) {
  var numchildren = Math.max(2, Math.floor(Math.random() * maxChildren));
  for (var i = 0; i < numchildren; i++) {
    if (count >= max) return count;
    count++;
    var childdata = { key: count, category: "Simple", text: `Node ${count}`, parent: parentdata.key, index: i };
    nodeDataArray.push(childdata);
    if (level > 0 && Math.random() > 0.25) {
      count = makeTree(level - 1, count, max, maxChildren, nodeDataArray, childdata);
    }
  }
  return count;
}

{ // create a random tree
  const nodeDataArray = [{ key: 0, text: "Root", index: 0}];
  makeTree(3, 0, 49, 5, nodeDataArray, nodeDataArray[0]);
  myDiagram.model = new go.TreeModel(nodeDataArray);
}

  </script>
</body>
</html>

Thanks @walter will try this out. We also have a requirement where the similar arrow like adornments have to be there all the time permanently. Is there a way to have adornments like this on the node all the time. If so how can we achieve that without basing on mouse events?

If you really want some decorations to be present for every node all the time, just include them in the node template.