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.
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
// container is either a Group or a Diagram
routeLinks(links, container) {
// if container is a Group, operate on those links that are members of that Group
// if container is a Diagram, operate on those links that are top-level (i.e. not in a Group)
}
}
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.
class CustomTreeRouter extends go.Router {
constructor() {
super();
this.name = "CustomTree";
}
canRoute(container) {
if (!super.canRoute(container)) return false;
if (container instanceof go.Diagram) return true;
return false; // only perform routing on the whole Diagram, never on Groups
}
// We do not use the second `container` argument in this router, because we implemented
// canRoute to ignore groups
routeLinks(links, container) {
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());
}
});
}
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.
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.