Ordering root nodes of layered digraph

Hi,

I have a quick question related to ordering root nodes of layered digraph. What is the best approach, is it through the assignLayers field of the layout?

I’m not sure if this is something we have easy control over since there is a lot of routing logic underneath the hood for optimization.

For example, for the following sample: Layered Digraph

How would we sort root nodes in ascending order based on the root node value?

My case is I have a certain root node I always want at the bottom and I don’t necessarily care about the ordering of the other root nodes.

Hmmm, that isn’t clear. When I get a chance I’ll investigate. I’ll first try adding that particular root node last and its links last to the LayeredDigraphNetwork. But even then the crossing minimizations could cause it to change order. If you don’t want to do those optimizations, you can override LayeredDigraphLayout.reduceCrossings to be a no-op.

<!DOCTYPE html>
<html>
<head>
  <title>Ordered First Layer LayeredDigraphLayout</title>
  <!-- Copyright 1998-2025 by Northwoods Software Corporation. -->
</head>
<body>
  <div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:400px"></div>
  Orders the nodes by data.text in reverse order for only those nodes in the first layer.
  (I.e. tries to put Alpha at the bottom.)
  <button id="myTestButton">Toggle Diagram.layout whether ordered</button>
  <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">
// This sorts the nodes in the first layer by reverse order of their data.text property value.
class FirstLayerOrderedLDLayout extends go.LayeredDigraphLayout {
  constructor(init) {
    super();
    if (init) Object.assign(this, init);
  }

  initializeIndices() {
    super.initializeIndices();
    const top = this.maxLayer;
    const arr = [];
    this.network.vertexes.each(v => {
      if (v.layer === top) {
        arr[v.index] = v;
      }
    });
    arr.sort((a, b) => {
      const atext = a.node?.data.text || "";
      const btext = b.node?.data.text || "";
      return btext.localeCompare(atext);
    });
    arr.forEach((v, i) => {
      v.index = i;
    });
  }
}

const myDiagram =
  new go.Diagram("myDiagramDiv", {
      layout: new FirstLayerOrderedLDLayout(),
      "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 =
  new go.Node("Auto")
    .bindTwoWay("location", "loc", go.Point.parse, go.Point.stringify)
    .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" },
  { key: 6, text: "Zeta", color: "white" },
  { key: 7, text: "Eta", color: "white" },
  { key: 8, text: "Theta", color: "white" },
],
[
  { from: 1, to: 2 },
  { from: 3, to: 4 },
  { from: 5, to: 2 },
  { from: 5, to: 3 },
  { from: 5, to: 8 },
  { from: 6, to: 3 },
  { from: 6, to: 4 },
  { from: 6, to: 8 },
  { from: 7, to: 8 },
  { from: 8, to: 4 },
]);

document.getElementById("myTestButton").addEventListener("click", e => {
  myDiagram.commit(diag => {
    if (diag.layout instanceof FirstLayerOrderedLDLayout) {
      diag.layout = new go.LayeredDigraphLayout();
    } else {
      diag.layout = new FirstLayerOrderedLDLayout();
    }
  });
});
  </script>
</body>
</html>