Trees and TreeLayout

There is no limit to the kinds of graphs that you can build in GoJS. But the most common kind of graph forms a "tree". A tree is a graph where each node may have at most one "tree parent" and at most one link connecting to that parent node, and where there are no cycles within the graph.

Because trees occur so frequently in diagrams, there is also a predefined tree layout that offers many customizations specifically for trees.

Manual layout of a tree structure

You can of course position the nodes manually, either by hand or programmatically. In this first example, the node locations are stored in the node data, and there is a Binding of Part.location to the node data property.


  diagram.nodeTemplate =
    new go.Node("Auto")
      .bind("location", "loc", go.Point.parse)
      .add(
        new go.Shape("Ellipse", { fill: "white" }),
        new go.TextBlock()
          .bind("text", "key")
      );

  diagram.linkTemplate =
    new go.Link({ routing: go.Routing.Orthogonal, corner: 5 })
      .add(
        new go.Shape()
      );

  const nodeDataArray = [
    { key: "Alpha", loc: "0 60" },
    { key: "Beta", loc: "100 15" },
    { key: "Gamma", loc: "200 0" },
    { key: "Delta", loc: "200 30" },
    { key: "Epsilon", loc: "100 90" },
    { key: "Zeta", loc: "200 60" },
    { key: "Eta", loc: "200 90" },
    { key: "Theta", loc: "200 120" }
  ];
  const linkDataArray = [
    { from: "Alpha", to: "Beta" },
    { from: "Beta", to: "Gamma" },
    { from: "Beta", to: "Delta" },
    { from: "Alpha", to: "Epsilon" },
    { from: "Epsilon", to: "Zeta" },
    { from: "Epsilon", to: "Eta" },
    { from: "Epsilon", to: "Theta" }
  ];
  diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);

You can also get the same results by using a TreeModel. Note how the node template and the link template are exactly the same as when using a GraphLinksModel, above.


  diagram.nodeTemplate =
    new go.Node("Auto")
      .bind("location", "loc", go.Point.parse)
      .add(
        new go.Shape("Ellipse", { fill: "white" }),
        new go.TextBlock()
          .bind("text", "key")
      );

  diagram.linkTemplate =
    new go.Link({ routing: go.Routing.Orthogonal, corner: 5 })
      .add(
        new go.Shape()
      );

  const nodeDataArray = [
    { key: "Alpha", loc: "0 60" },
    { key: "Beta", loc: "100 15", parent: "Alpha" },
    { key: "Gamma", loc: "200 0", parent: "Beta" },
    { key: "Delta", loc: "200 30", parent: "Beta" },
    { key: "Epsilon", loc: "100 90", parent: "Alpha" },
    { key: "Zeta", loc: "200 60", parent: "Epsilon" },
    { key: "Eta", loc: "200 90", parent: "Epsilon" },
    { key: "Theta", loc: "200 120", parent: "Epsilon" }
  ];
  diagram.model = new go.TreeModel(nodeDataArray);

Automatic TreeLayout

It is most common to use TreeLayout for laying out trees. Just assign Diagram.layout to an instance of TreeLayout. This example also defines the setupTree function that is used in later examples on this page.


function setupTree(diagram) {
  diagram.nodeTemplate =
    new go.Node("Auto")
      .add(
        new go.Shape("Ellipse", { fill: "white" }),
        new go.TextBlock()
          .bind("text", "key")
      );

  diagram.linkTemplate =
    new go.Link({ routing: go.Routing.Orthogonal, corner: 5 })
      .add(
        new go.Shape()
      );

  const nodeDataArray = [
    { key: "Alpha" },
    { key: "Beta", parent: "Alpha" },
    { key: "Gamma", parent: "Beta" },
    { key: "Delta", parent: "Beta" },
    { key: "Epsilon", parent: "Alpha" },
    { key: "Zeta", parent: "Epsilon" },
    { key: "Eta", parent: "Epsilon" },
    { key: "Theta", parent: "Epsilon" }
  ];
  diagram.model = new go.TreeModel(nodeDataArray);
}

setupTree(diagram);
// automatic tree layout
diagram.layout = new go.TreeLayout();

Common TreeLayout properties

The TreeLayout.angle property controls the general direction of tree growth. This must be zero (towards the right), 90 (downward), 180 (leftward), or 270 (upward).


  setupTree(diagram);
  diagram.layout = new go.TreeLayout({ angle: 90 });

The setupTree function was defined above.

The TreeLayout.alignment property controls how the parent node is positioned relative to its children. This must be one of the Alignment... constants defined on TreeLayout.


  setupTree(diagram);
  diagram.layout = new go.TreeLayout({ angle: 90, alignment: go.TreeAlignment.Start });

For tree layouts, all of the nodes are placed into "layers" according to the length of the chain of links from the root node. These layers are not to be confused with Diagram Layers, which control the Z-ordering of the nodes. The TreeLayout.layerSpacing property controls how close the layers are to each other. The TreeLayout.nodeSpacing property controls how close nodes are to each other in the same layer.


  setupTree(diagram);
  diagram.layout = new go.TreeLayout({ layerSpacing: 20, nodeSpacing: 0 });

The children of each node can be sorted. By default the TreeLayout.comparer function compares the Part.text property. So if that property is data bound by the node template, and if you set the TreeLayout.sorting property to sort in either ascending or descending order, each parent node will have all of its children sorted in that order by their text strings. (In this example that means alphabetical ordering of the English names of the letters of the Greek alphabet.)


  setupTree(diagram);
  diagram.nodeTemplate =
    new go.Node("Auto")
      .bind("text", "key")  // bind Part.text to support sorting
      .add(
        new go.Shape("Ellipse", { fill: "white" }),
        new go.TextBlock()
          .bind("text", "key")
      );
  diagram.layout = new go.TreeLayout({ sorting: go.TreeSorting.Ascending });

But you can provide your own function for ordering the children, such as:


  new go.Diagram(. . .,
    {
      layout:
          new go.TreeLayout({
              sorting: go.TreeSorting.Ascending,
              comparer: (a, b) => {
                  // A and B are TreeVertexes
                  const av = a.node.data.someProp;
                  const bv = b.node.data.someProp;
                  if (av < bv) return -1;
                  if (av > bv) return 1;
                  return 0;
                },
              . . .
            })
      . . .
    })