Dynamic Layer Spacing for Tree Layout

Here you go, a complete stand-alone sample:

<!DOCTYPE html>
<html>

<head>
  <title>Splice Node into Link with DropZone</title>
  <!-- Copyright 1998-2024 by Northwoods Software Corporation. -->
</head>

<body>
  <div style="width: 100%; display: flex; justify-content: space-between">
    <div style="display: flex; flex-direction: column; margin: 0 2px 0 0">
      <div id="myPaletteDiv" style="flex-grow: 1; width: 100px; background-color: floralwhite; border: solid 1px black"></div>
      <div id="myOverviewDiv" style="margin: 2px 0 0 0; width: 100px; height: 100px; background-color: whitesmoke; border: solid 1px black"></div>
    </div>
    <div id="myDiagramDiv" style="flex-grow: 1; height: 400px; border: solid 1px black"></div>
  </div>
  <textarea id="mySavedModel" style="width:100%;height:250px"></textarea>

  <script src="go.js"></script>
  <script src="../site/extensions/NonRealtimeDraggingTool.js"></script>
  <!-- <script src="https://unpkg.com/gojs"></script>
  <script src="https://unpkg.com/gojs/extensions/NonRealtimeDraggingTool.js"></script> -->
  <script id="code">
class DropZoneDraggingTool extends NonRealtimeDraggingTool {
  constructor(init) {
    super();
    this.subtreeOffset = new go.Point(100, 0);
    const $ = go.GraphObject.make;
    this.DropZone =
      $(go.Adornment, "Auto",
        { locationSpot: go.Spot.Center },
        $(go.Shape, "RoundedRectangle",
          { fill: "white", strokeWidth: 5, stroke: "lightblue" }),
        $(go.Panel, "Auto",
          { width: 100, height: 60, margin: 5 },
          $(go.Shape, "RoundedRectangle",
            { fill: "white", stroke: "lightblue", strokeDashArray: [4, 2] }),
          $(go.Panel, "Spot",
            $(go.Shape,
              { fill: "white", stroke: "lightgray", strokeDashArray: [4, 2], width: 30, height: 20 }),
            $(go.Shape,
              {
                alignment: new go.Spot(0.5, 0.5, 0, -10),
                geometryString: "M5 0L5 20 M0 15 L5 20 10 15",
                stroke: "lightgray", strokeWidth: 2
              })
          )
        )
      );
    this.DropZone.ensureBounds();
    // internal state
    this._subtree = null;
    this._draggedNode = null;
    if (init) Object.assign(this, init);
  }

  // User must drag a single Node
  findDraggablePart() {
    if (this.diagram.selection.count > 1) return null;
    const part = super.findDraggablePart();
    if (part instanceof go.Node) {
      this._draggedNode = part;
      return part;
    }
    return null;
  }

  // Show a DropZone if dragging over a Link (other than one connected with _draggedNode)
  doDragOver(pt, obj) {  // method override
    // find Part at PT, ignoring temporary Parts except for the DropZone (temporary because it's an Adornment)
    var trgt = this.diagram.findObjectAt(pt, x => x.part, x => x === this.DropZone || !x.layer.isTemporary);
    if (trgt instanceof go.Link) {
      const link = trgt;
      if (link.fromNode === this._draggedNode || link.toNode === this._draggedNode) return;
      this._link = link;
      if (this.DropZone.adornedPart === null) {
        const needsShift = link.routeBounds.width < this.DropZone.actualBounds.width + 100 ||
                           link.routeBounds.height < this.DropZone.actualBounds.height + 100;
        const oldskips = this.diagram.skipsUndoManager;
        this.diagram.skipsUndoManager = true;
        if (needsShift) {
          this._subtree = link.toNode.findTreeParts();
          // shift subtree rightward
          this.diagram.moveParts(this._subtree, this.subtreeOffset);
        }
        link.isHighlighted = true;
        this.DropZone.adornedObject = link;
        link.addAdornment("DropZone", this.DropZone);
        this.diagram.skipsUndoManager = oldskips;
        // if nodes were moved, the link will be re-routed later, so the Link.midPoint will only be valid later
        if (needsShift) setTimeout(() => this.DropZone.location = link.midPoint);
      }
    } else if (trgt !== this.DropZone) {
      this.cleanup();
    }
  }

  cleanup() {
    const link = this.DropZone.adornedPart;
    if (link) {
      const oldskips = this.diagram.skipsUndoManager;
      this.diagram.skipsUndoManager = true;
      if (this._subtree) {
        // shift subtree leftward
        this.diagram.moveParts(this._subtree, new go.Point(-this.subtreeOffset.x, -this.subtreeOffset.y));
        this._subtree = null;
      }
      link.isHighlighted = false;
      link.removeAdornment("DropZone");
      this.DropZone.adornedObject = null;
      this.diagram.skipsUndoManager = oldskips;
    }
  }

  // If dropped into DropZone, splice it into the corresponding Link
  // (Note, not using doDropOnto due to undo problems.
  // Overriding doMouseUp means needing "ExternalObjectsDropped" listener too,
  // duplicating some of the work.)
  doMouseUp() {  // method override
    const link = this.DropZone.adornedPart;
    const node = this._draggedNode;
    const pt = this.diagram.lastInput.documentPoint;
    const trgt = this.diagram.findObjectAt(pt, x => x.part, x => x === this.DropZone);
    if (trgt === this.DropZone) {
      this.cleanup();
      this.spliceIntoLink(link, node);
    } else {
      this.cleanup();
    }
    super.doMouseUp();
  }

  // Splice the _draggedNode into the dropped-onto Link
  spliceIntoLink(link, node) {
    if (!link || !node) return;
    const diag = this.diagram;
    if (!diag) return;
    // disconnect node being dropped (copy collection to avoid iterating over modifications)
    new go.List(node.findLinksConnected()).each(l => diag.remove(l));
    const to = link.toNode;
    const linkdata = {};
    diag.model.addLinkData(linkdata);
    const newlink = diag.findLinkForData(linkdata);
    if (newlink !== null) {
      // splice in that node
      link.toNode = node;
      newlink.fromNode = node;
      newlink.toNode = to;
    }
  }

  doDeactivate() {
    this._subtree = null;
    this._draggedNode = null;
    super.doDeactivate();
  }

  doCancel() {
    this.cleanup();
    super.doCancel();
  }
}


const $ = go.GraphObject.make;

const myDiagram =
  new go.Diagram("myDiagramDiv",
    {
      layout: new go.TreeLayout(),
      // install the replacement DraggingTool:
      draggingTool: new DropZoneDraggingTool({ duration: 400 }),
      "ExternalObjectsDropped": e => {
        const tool = e.diagram.toolManager.draggingTool;
        const pt = e.diagram.lastInput.documentPoint;
        const trgt = e.diagram.findObjectAt(pt, x => x.part, x => x === tool.DropZone);
        if (trgt === tool.DropZone) {
          const link = tool.DropZone.adornedPart;
          const node = e.diagram.selection.first();
          tool.cleanup();
          tool.spliceIntoLink(link, node);
        }
      },
      "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();
        }
      }
    });

myDiagram.nodeTemplate =
  $(go.Node, "Auto",
    { locationSpot: go.Spot.Center },
    $(go.Shape, { fill: "white" },
      new go.Binding("fill", "color")),
    $(go.TextBlock, { margin: 8 },
      new go.Binding("text"))
  );

myDiagram.linkTemplate =
  $(go.Link,
    // the highlight path Shape
    $(go.Shape, { isPanelMain: true, strokeWidth: 7, stroke: "transparent" },
      // when highlighted, show this thick Shape in red
      new go.Binding("stroke", "isHighlighted", h => h ? "red" : "transparent").ofObject()),
    // the normal path Shape
    $(go.Shape, { isPanelMain: true, strokeWidth: 1.5 }),
    $(go.Shape, { toArrow: "OpenTriangle" })
  );

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" },
    { key: 6, text: "Zeta", color: "lightblue" },
    { key: 7, text: "Eta", color: "orange" },
    { key: 8, text: "Theta", color: "lightgreen" },
  ],
  [
    { from: 1, to: 2 },
    { from: 1, to: 3 },
    { from: 3, to: 4 },
    { from: 4, to: 5 },
    { from: 1, to: 6 },
    { from: 6, to: 7 },
    { from: 6, to: 8 },
  ]);

// initialize Palette
myPalette =
  new go.Palette("myPaletteDiv",
    {
      nodeTemplateMap: myDiagram.nodeTemplateMap,
      model: new go.GraphLinksModel([
        { text: "red node", color: "red" },
        { text: "green node", color: "green" },
        { text: "blue node", color: "blue" },
        { text: "orange node", color: "orange" }
      ])
    });

// initialize Overview
myOverview =
  new go.Overview("myOverviewDiv",
    {
      observed: myDiagram,
      contentAlignment: go.Spot.Center
    });  </script>
</body>

</html>