Position button in tree layout

Hello!

I’ve got a tree layout, and want to position a button for adding a new tree node (see my poorly drawn picture). It should be placed in the “second column”, so of the children of the root. There is always just one root node.

Nodes and lines are drawn with templates and data (similar to this example, but with more styling).

So far, I’ve tried to add a new node, and then positioned it based on the nodes in the “second column”.

// add the AddButton
diagram.add(new Go.Node(....));

// position add button when data/nodes are updated
diagram.addModelChangedListener(() => {
const furthestDownNodeInSecondColumn = ....;
const addButton = ....;

// doesn't work, since position is NaN, NaN
addButton.position = furthestDownNodeInSecondColumn.position;
});

Is it possible to do this the current way I’m trying? Like is there a way to expose the position of the nodes?

Or is there another way to make this happen?

Do you want to have an “Add” button at the bottom of each parent Node’s column of children? As if each Node had an extra child Node whose only purpose is to show that “Add” button?

Not sure I get your question correctly, but only the second “column” (so the column with the children of the root) should have the add button. So in the image only the “column” with Beta/Epsilon should have the add button, the “columns” with Alpha and Delta/Gamma/etc should not.

One thing that is important is that the button shouldn’t cause the other elements to move. So in the example above the tree layout shouldn’t behave as though there were three children to the root node.

Does that make sense? I’m not very knowledgeable in the lingo of GoJS so sorry if I’m calling some things the wrong name!

I assume your diagram has an automatic layout, so that after the user clicks the Add button, the button is moved appropriately down to make room for the new child node.

I would have that button not be in a Node, but in a simple Part. And I would customize the layout so that it always positions the button.

I’ll create a sample for you.

<!DOCTYPE html>
<html>
<head>
  <title>A single "Add Child" button for the root node</title>
  <!-- Copyright 1998-2025 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://cdn.jsdelivr.net/npm/gojs/release/go-debug.js"></script>
  <script id="code">
const myDiagram =
  new go.Diagram("myDiagramDiv", {
      layout: new go.TreeLayout(),
      "LayoutCompleted": e => {
        const root = e.diagram.findTreeRoots().first();
        if (!root) return;
        if (root.findTreeChildrenNodes().count > 0) {
          const b = e.diagram.computePartsBounds(root.findTreeChildrenNodes());
          AddButton.position = new go.Point(b.x, b.bottom + 30);
        } else {
          AddButton.position = new go.Point(root.actualBounds.right + 30, root.actualBounds.y);
        }
      },
      "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();
        }
      }
    });

const AddButton = // not a Node, so it is not laid out by the TreeLayout
  new go.Part("Spot", {
      selectable: false,
      click: (e, part) => {
        const root = e.diagram.findTreeRoots().first();
        if (root) {
          e.diagram.commit(diag => {
            const childdata = { text: "New", color: "lavender" };
            diag.model.addNodeData(childdata);
            const linkdata = { from: root.key, to: diag.model.getKeyForNodeData(childdata) };
            diag.model.addLinkData(linkdata);
          });
        }
      }
    })
    .add(
      new go.Shape("Circle", { fill: "whitesmoke", strokeWidth: 2, width: 36, height: 36 }),
      new go.Shape("PlusLine", { strokeWidth: 2, width: 24, height: 24 })
    );
myDiagram.add(AddButton);

// the design of the nodes doesn't really matter for this demonstration
myDiagram.nodeTemplate =
  new go.Node("Auto")
    .add(
      new go.Shape({ fill: "white" })
        .bind("fill", "color"),
      new go.TextBlock({ margin: 8 })
        .bind("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" },
  { key: 5, text: "Epsilon", color: "yellow" },
],
[
  { from: 1, to: 2 },
  { from: 1, to: 3 },
  { from: 3, to: 4 },
  { from: 3, to: 5 },
]);
  </script>
</body>
</html>


Yes exactly!

Okay, interesting! Big thanks!