Avoid overlapping issue for Link and Link Label

I want to avoid overlapping of link labels, as the label size is too large. Please suggest a way to make the link labels clearly visible and readable. Refer to the details below for your reference.

Link Template Code: -

const linkTemplate =
  $(go.Link, {
      routing: go.Link.Orthogonal,
      corner: 5,
      selectionAdorned: false
    },
    new go.Binding("fromSpot", "category", (data) => data === "SelfLink" ? go.Spot.BottomSide : go.Spot.BottomSide),
    new go.Binding("toSpot", "category", (data) => data === "SelfLink" ? go.Spot.Left : go.Spot.TopSide),
    new go.Binding("zOrder", "isSelected", (sel) => sel ? 1 : 0).ofObject(), //After select link come in front
    new go.Binding("layerName", "isSelected", (sel) => sel ? "Foreground" : "Background").ofObject(), // Bring link to front when selected
    $(go.Shape,
      {
        isPanelMain: true, strokeWidth: 1.5,
        fromLinkable: true,
        toLinkable: true,
        pickable: true,
      },
      new go.Binding("fill", "isSelected",
        (sel, shp) => sel ? "#6c1448" : shp.part.data.edgedetails.color).ofObject(),
      new go.Binding("stroke", "isSelected",
        (sel, shp) => sel ? "#6c1448" : shp.part.data.edgedetails.color).ofObject(),
      new go.Binding("strokeWidth", "isSelected", s => s ? 3 : 1.5).ofObject(),
    ),
    $(go.Shape,  //to arrowhead
      {
        toArrow: "standard", strokeWidth: 1
      },
      new go.Binding("fill", "isSelected",
        (sel, shp) => sel ? "#6c1448" : shp.part.data.edgedetails.color).ofObject(),
      new go.Binding("stroke", "isSelected",
        (sel, shp) => sel ? "#6c1448" : shp.part.data.edgedetails.color).ofObject(),
      new go.Binding("scale", "isSelected", s => s ? 1.60 : 1).ofObject(),
    ),
    $(go.Panel, "Auto",
      {
        name: "LABEL"
      },
      $(go.Shape, "RoundedRectangle",
        {
          strokeWidth: 2,
          visible: false,
          pickable: true,
          background: "transparent",
        },
        new go.Binding("fill", "isSelected",
          (sel, shp) => sel ? "#6c1448" : "#ffffff").ofObject(),
        new go.Binding("layerName", "isSelected", (sel) => sel ? "Foreground" : "Background").ofObject(),

      ),
      $(go.TextBlock,  // the label
        {
          background: "#ffffff",
          height: 18,
          textAlign: "center",
          font: "bold 10pt serif",
          stroke: "#000000",
          editable: false,
          margin: 2
        },
        new go.Binding("font", "isSelected",
          (sel, shp) => sel ? "bold 11pt serif" : "bold 10pt serif").ofObject(),
        new go.Binding("stroke", "isSelected",
          (sel, shp) => sel ? "#6c1448" : "#000000").ofObject(),
        new go.Binding("text", "constraintname").makeTwoWay()
      )
    )
  );

With the link path routes running so close to each other, I’m not sure you can avoid all cases of overlapping labels. Can the routes be spread out more?

Assuming that is not possible, there are at least two possibilities. (1) set each label’s segmentOffset or segmentFraction so that the labels don’t overlap each other, or (2) in v3.0 use the LinkLabelRouter extension, although that currently has the disadvantage of not always keeping the label on the route.

By the way, there’s probably no reason to have the label be an “Auto” Panel when the main (border) Shape is not visible. Either replace the “Auto” Panel with just the TextBlock as the label, or remove the border Shape and don’t specify the Panel type to be “Auto” (or anything else but the default “Position”).

Hi walter,

I am using segmentOffset: new go.Point(0, 10), which helps a little in resolving link label overlapping issues. However, I only want to apply segmentOffset when labels overlap, because in cases where there is no overlap, it still pushes the label away from the link.

Please refer SS:

Which version of GoJS are you using?

I am using version v3.1.2

Have you tried the LinkLabelRouter extension?

Recently, after implementing LinkLabelRouter, I encountered an issue where selecting a link in large diagrams takes nearly 2–3 minutes, and the diagram page becomes unresponsive.

Is there any specific reason behind this?

Code to implement LinkLabelRouter:


this.myDiagram.startTransaction(“addRouter”);
let router = new LinkLabelRouter({ layoutProps: { defaultElectricalCharge: 50} })
this.myDiagram.routers.add(router);
this.myDiagram.links.each(function(link) {
// @ts-ignorets-ignorets-ignorets-ignore
if(link.part.data.text != “”) {
link.invalidateRoute();
}
});
this.myDiagram.commitTransaction(“addRouter”);

Link Template:


$(go.Link, {
    routing: go.Routing.Orthogonal,
    corner: 5,
    selectionAdorned: false
  },
  new go.Binding(“zOrder”, “isSelected”,
    (sel, shp) => sel ? 1 : 0).ofObject(), //After select link come in front
  new go.Binding(“layerName”, “isSelected”,
    (sel) => sel ? “Foreground” :   “Background”).ofObject(), // Bring link to front when selected
  $(go.Shape, {
    isPanelMain: true, strokeWidth: 1.5,
    fromLinkable: true, toLinkable: true,
  },
  new go.Binding(“fill”, “isSelected”,
    (sel, shp) => sel ? “#6c1448” : shp.part.data.edgedetails.color).ofObject(),
  new go.Binding(“stroke”, “isSelected”,
    (sel, shp) => sel ? “#6c1448” : shp.part.data.edgedetails.color).ofObject(),
  new go.Binding(“strokeWidth”, “isSelected”, s => s ? 3 : 1.5).ofObject(),
),

If you temporarily remove the Link’s binding of layerName, does that delay ever happen?

Even after removing Link’s binding of layerName, the link selection was still delayed.

I have edited your code so that it is more legible.

You don’t need both bindings of Link.layerName and Link.zOrder, so I would just delete that zOrder binding.

There’s no point in setting or binding the Link’s path Shape’s Shape.fill property – the standard computed Geometry will never be fill-able. So I would delete that too.

Also, because you haven’t customized the Link’s selection Adornment, there’s no benefit in binding the Link’s path Shape’s Shape.stroke or Shape.strokeWidth either. So I would delete those two too.

Anyway, I tried to see if I could reproduce your problem, but I could not. Here’s my code:

<!DOCTYPE html>
<html>
<head>
  <title>Testing Routers</title>
  <!-- Copyright 1998-2026 by Northwoods Software Corporation. -->
</head>
<body>
  <div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:1000px"></div>

  <script src="https://cdn.jsdelivr.net/npm/gojs/release/go-debug.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/create-gojs-kit/dist/extensions/AvoidsLinksRouter.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/create-gojs-kit/dist/extensions/LinkLabelRouter.js"></script>
  <script id="code">
const myDiagram =
  new go.Diagram("myDiagramDiv", {
      "animationManager.isInitial": false,
      initialScale: 0.5,
      "undoManager.isEnabled": true
    });
myDiagram.routers.add(new AvoidsLinksRouter());
myDiagram.routers.add(new LinkLabelRouter());

myDiagram.nodeTemplate =
  new go.Node("Auto", { fromSpot: go.Spot.Right, toSpot: go.Spot.Left })
    .bind("location", "loc", go.Point.parse)
    .add(
      new go.Shape({ fill: "lightgreen" })
        .bind("fill", "color"),
      new go.TextBlock({ margin: 8 })
        .bind("text", "key")
    );

function highlightLink(link, show) {
  if (show) {
    link.path.stroke = link.elt(2).stroke = "red";
    link.path.strokeWidth = 5;
  } else {
    link.path.stroke = link.elt(2).stroke = "black";
    link.path.strokeWidth = 1;
  }
}

myDiagram.linkTemplate =
  new go.Link({
      routing: go.Routing.Orthogonal, corner: 10,
      mouseEnter: (e, link) => highlightLink(link, true),
      mouseLeave: (e, link) => highlightLink(link, false),
    })
    .bindObject("layerName", "isSelected", s => s ? "Foreground" : "")
    .add(
      new go.Shape(),
      new go.Shape({ toArrow: "OpenTriangle" }),
      new go.TextBlock()
        .bind("text")
        .bindObject("background", "isSelected", s => s ? "white" : null)
    );

const ROWS = 500;
const nda = [];
for (let i = 0; i < ROWS; i++) {
  nda.push({ key: i, loc: go.Point.stringify(new go.Point(0, i*10)) })
  nda.push({ key: i+ROWS, loc: go.Point.stringify(new go.Point(2000, i*10)) })
}
const lda = [];
for (let i = 0; i < nda.length/2; i++) {
  let j = Math.floor(nda.length/2 + Math.random() * nda.length/2);
  lda.push({ from: i, to: j, text: `${i} --> ${j}` });
}

myDiagram.model = new go.GraphLinksModel(nda, lda);
  </script>
</body>
</html>