Custom Routers

Each Link performs a very fast default computation of its desired path, its "route", based only on the properties of the Link and the properties of the port objects that it is connected with, as discussed in the pages on Links and on Link Connection Points.

GoJS 3.0 provides a way to customize link routing by allowing consideration of other Nodes and Links, with the Router class. Routers can be created and added to the Diagram's Diagram.routers list, and these will operate on links during updates.

Routing Basics

Routers work by defining a method, Router.routeLinks, which takes a collection of recently recomputed link routes, plus a collection context that is either a Group or the Diagram. This method is called by the Diagram during the update phase after layouts are performed.


  class MyRouter extends go.Router {
    // links is a Set of Links that may need to be re-routed to avoid other Links
    // coll is either a Group or a Diagram or undefined
    routeLinks(links, coll) {
      // if coll is a Group, operate on those links that are also members of that Group
      // if coll is a Diagram, operate on those links that are also top-level (i.e. not in a Group)
      // if coll is undefined, operate on all of the given links
    }
  }

During updates to the Diagram, GoJS does a depth-first walk of all Groups in the Diagram, starting with the leaf-most Groups, and performs their layouts if invalid. For each of the routers present in Diagram.routers, the Diagram calls Router.canRoute, passing it the Group. If that predicate returns true, it calls Router.routeLinks. Finally, the Diagram performs the Diagram.layout if it is invalid, and calls Router.canRoute and possibly Router.routeLinks with the Diagram itself.

Group bounds are normally affected by their member links because Group.computesBoundsIncludingLinks is true by default. You can set this property to false if you do not want or need the bounds of member links to affect the bounds of any Groups.

Simple Router Example

The sample below shows a simple use case of a Router, which will cause the vertical segments of links in a TreeLayout to be positioned at a fixed distance from the next node in the tree. This custom router is designed to only operate on the whole Diagram, not individual Groups.

  class CustomTreeRouter extends go.Router {
    constructor() {
      super();
      this.name = "CustomTree";
    }

    canRoute(coll) {
      if (!super.canRoute(coll)) return false;
      if (coll instanceof go.Diagram) return true;
      return false; // only perform routing on the whole Diagram, never on Groups
    }

    // We do not use the second `coll` argument in this router, because we implemented
    // canRoute to ignore groups
    routeLinks(links, coll) {
      links.each(link => {
        if (!link.isOrthogonal) return; // only applies to orthogonal links

        // assume links are going left to right
        const childX = link.getPoint(link.pointsCount - 1).x;

        const p2 = link.getPoint(2);
        const p3 = link.getPoint(3);
        if (Math.abs(p2.x - p3.x) < 0.01) { // don't route horizontal segments
          link.startRoute();
          link.setPoint(2, new go.Point(childX - 10, p2.y));
          link.setPoint(3, new go.Point(childX - 10, p3.y));
          link.commitRoute();
        }
      });
    }
  }  // end of CustomTreeRouter


  diagram.routers.add(new CustomTreeRouter());

  diagram.layout = new go.TreeLayout();

  diagram.nodeTemplate =
    new go.Node("Auto").add(
      new go.Shape("RoundedRectangle", { strokeWidth: 0 })
        .bind("fill", "color"),
      new go.TextBlock({ margin: 8 })
        .bind("text", "key")
    );

  diagram.linkTemplate =
    new go.Link({ routing: go.Routing.Orthogonal }).add(
      new go.Shape({ strokeWidth: 1.5, stroke: '#444' })
    );

  diagram.model = new go.GraphLinksModel(
    [
      { key: "A", color: "lightblue" },
      { key: "B", color: "orange" },
      { key: "C", color: "lightgreen" },
      { key: "D", color: "pink" },
      { key: "E", color: "lightblue" },
      { key: "F", color: "orange" }
    ],
    [
      { from: "A", to: "B" },
      { from: "A", to: "C" },
      { from: "B", to: "D" },
      { from: "C", to: "E" },
      { from: "C", to: "F" }
    ]
  );

  window.toggleRouter = () => {
    diagram.commit(diag => {
      const router = diag.findRouter("CustomTree");
      if (router instanceof CustomTreeRouter) {
        router.isEnabled = !router.isEnabled;
        // this is needed due to dynamically enabling/disabling routers
        diag.links.each(link => link.invalidateRoute());
      }
    });
  }

Avoids Links Router

When creating diagrams with many Links using Orthogonal or AvoidsNodes routing, it is common to have segments of separate links overlapping. GoJS provides an extension, the AvoidsLinksRouter, which will cause such segments to instead be routed in parallel while minimizing the number of crossings between segments.

For a demonstration of the AvoidsLinksRouter in a diagram with many links, see the Avoids Links Router sample.

Link Label Router

When creating diagrams with many Links that have labels on them, it is common to have those labels sometimes overlapping each other. GoJS provides an extension, the LinkLabelRouter, which will cause such labels to be shifted slightly in order to reduce the overlaps.

Although that Router does not actually modify any Link routes, it may modify the bounds of some Links, and it can only do so after the default routing of all Links has already taken place.

For a demonstration of the LinkLabelRouter in a diagram with many links, see the Link Label Router sample.