Dynamically set segmentIndex

Hi,

Is there any way to set the segmentIndex dynamically using binding or function?

The diagram below has two types of links:

  1. A link goes down directly and
  2. A link first goes right and then down

I want to put the label always in the vertical segment. For type 1 link, the segmentIndex should be NaN; and for type 2 link, the segmentIndex should be 1. I can differentiate the links using its fromPort. But how to set segmentIndex dynamically? Is there any example code that I can take a look?

My backup approach is to use two different link templates. But I feel the dynamic segmentIndex is a simpler approach.

Thanks so much for your time!

Yes, you could use a Binding whose target property is GraphObject.segmentIndex on that label object. But I’m unclear exactly what the binding source would be and how you would decide between type 1 and type 2. (And it isn’t shown which one is which.)

Alternatively you could define a subclass of Link and override the Link.computePoints method. Call the super method, and if it returned true, find the label and set its segmentIndex property, and then return the result of the super call.

Hi Walter, thank you so much for your quick response. I will investigate more based on your suggestions. Have a good weekend!

Hi Walter,

I have a different question related to segmentIndex. In the diagram below, the right arm (from Alpha to Gamma) is a custom link with its link.points specified

const pts = link.points.copy();
pts.clear();
pts.add(new go.Point(200, 125));
pts.add(new go.Point(350, 125));
pts.add(new go.Point(350, 200));
link.points = pts;

The left arm and the central vertical link are automatically routed by GoJS.

Screen Shot 2021-10-15 at 9.14.08 PM

When I manipulated the segmentIndex value in the link template, I observed some issues that I do not quite understand.

  myDiagram.linkTemplate = $(
    go.Link,
    { routing: go.Link.Orthogonal },
    $(go.Shape),
    $(go.Shape, { toArrow: "Standard" }),
    $(go.TextBlock, "label", { segmentIndex: NaN, segmentFraction: 0.5 })
  );

When I am using NaN as the value, the label is centered at all the links, which makes sense.

Screen Shot 2021-10-15 at 9.14.26 PM

When I change the segmentIndex value to 0, the label’s position in the right arm link makes sense to me because it is centered at the first segment. But why the labels in the other 2 links are positioned at the fromPort?

Screen Shot 2021-10-15 at 9.14.43 PM

When I change the segmentIndex value to 1, the label’s position in the right arm link makes sense to me too because it is centered at the second segment. But again, I do not understand how the labels in the other 2 links are positioned.

Screen Shot 2021-10-15 at 9.14.56 PM

For more references, the figures below show the label positions with segmentIndex to be 2, 3, and 4.

Screen Shot 2021-10-15 at 9.30.01 PM

Screen Shot 2021-10-15 at 9.36.06 PM

Screen Shot 2021-10-15 at 9.36.36 PM

How does GoJS count segments for automatically routed links? When an automatically routed link that has a horizontal part and a vertical part, that is, the left arm from Alpha to Delta, how can I make sure the label is positioned center in the vertical part?

I am pasting my HTML + JS code below. I am using NaN as the default segmentIndex value. But you could tweak that value if you want.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div
      id="myDiagramDiv"
      style="width: 1500px; height: 800px; background-color: #dae4e4"
    ></div>
    <script src="../../release/go-debug.js"></script>
    <script>
      function TestLayout() {
        go.Layout.call(this);
      }

      go.Diagram.inherit(TestLayout, go.Layout);

      TestLayout.prototype.doLayout = function () {
        let alpha;
        let beta;
        let gamma;
        let delta;
        const it = this.diagram.nodes.iterator;
        while (it.next()) {
          const node = it.value;
          if (node.key === "Alpha") {
            alpha = node;
          } else if (node.key === "Beta") {
            beta = node;
          } else if (node.key === "Gamma") {
            gamma = node;
          } else if (node.key === "Delta") {
            delta = node;
          }
        }

        this.diagram.startTransaction("Test Layout");

        alpha.moveTo(100, 100);
        beta.moveTo(100, 300);
        gamma.moveTo(300, 200);
        delta.moveTo(-100, 200);

        const linkIt = alpha.findLinksOutOf();

        while (linkIt.next()) {
          const link = linkIt.value;
          if (link.toNode.key === "Gamma") {
            const pts = link.points.copy();
            pts.clear();
            pts.add(new go.Point(200, 125));
            pts.add(new go.Point(350, 125));
            pts.add(new go.Point(350, 200));
            link.points = pts;
          }
        }

        this.diagram.commitTransaction("Test Layout");
      };

      const $ = go.GraphObject.make;

      const myDiagram = $(go.Diagram, "myDiagramDiv", {
        layout: $(TestLayout),
      });

      myDiagram.nodeTemplate = $(
        go.Node,
        "Position",
        $(go.Shape, "Rectangle", {
          width: 100,
          height: 50,
          fill: "white",
        }),
        $(go.TextBlock, { margin: 5 }, new go.Binding("text", "key")),
        // ports
        $(go.Shape, "Circle", {
          portId: "input",
          width: 10,
          height: 10,
          fill: "red",
          stroke: null,
          position: new go.Point(45, 0),
          toSpot: go.Spot.Top,
        }),
        $(go.Shape, "Circle", {
          portId: "output",
          width: 10,
          height: 10,
          fill: "blue",
          stroke: null,
          position: new go.Point(45, 40),
          fromSpot: go.Spot.Bottom,
        }),
        $(go.Shape, "Circle", {
          portId: "output2",
          width: 10,
          height: 10,
          fill: "green",
          stroke: null,
          position: new go.Point(90, 20),
          fromSpot: go.Spot.Right,
        }),
        $(go.Shape, "Circle", {
          portId: "output3",
          width: 10,
          height: 10,
          fill: "cyan",
          stroke: null,
          position: new go.Point(0, 20),
          fromSpot: go.Spot.Left,
        })
      );

      myDiagram.linkTemplate = $(
        go.Link,
        { routing: go.Link.Orthogonal },
        $(go.Shape),
        $(go.Shape, { toArrow: "Standard" }),
        $(go.TextBlock, "label", { segmentIndex: NaN, segmentFraction: 0.5 })
      );

      myDiagram.model = $(go.GraphLinksModel, {
        linkFromPortIdProperty: "fromPort",
        linkToPortIdProperty: "toPort",
        nodeDataArray: [
          { key: "Alpha" },
          { key: "Beta" },
          { key: "Gamma" },
          { key: "Delta" },
        ],
        linkDataArray: [
          { from: "Alpha", fromPort: "output", to: "Beta", toPort: "input" },
          { from: "Alpha", fromPort: "output2", to: "Gamma", toPort: "input" },
          { from: "Alpha", fromPort: "output3", to: "Delta", toPort: "input" },
        ],
      });
    </script>
  </body>
</html>

Thank you so much for your time!

Take a look at the Link.points list of Points for each of those Links. The normal routing for orthogonal links will have six points in them, so each one will have five segments. The end segments (i.e. the first and last segments) have their length determined by Link.computeEndSegmentLength, which may return the value of Link.fromEndSegmentLength or Link.toEndSegmentLength.

However, for that one link where you have specified the route, there are only three points or two segments, so a bogus segmentIndex is treated as if it were not specified – i.e. as a mid-point.

Hi Walter,

Thanks for your explanation. I got the 6 link points from the normal orthogonal routing as shown below. Because I set both fromEndSegmentLength and toEndSegmentLength to 0, I do see that the first two points are merged at the fromPort and the last two points are merged at the toPort. And I can also figure out all the segment indexes.

Here is a follow-up question: if the requirement is to display a label always at the center (50%) of the vertical part of the link, I can use

$(go.TextBlock, "label", { segmentIndex: 3, segmentFraction: 0 }).

But what if the requirement is that the label should be at the 35% of the vertical part of the link, should I do some complex calculation to put the label somewhere in segmentIndex 2? Is there a way I could treat the vertical part as a single segment so that I can put segmentFraction: 0.35?

Hopefully, I expressed myself clearly :) If not, please let me know.

Thanks for your time!

Hi Walter, could you please provide me some sample code on how to define a subclass of Link and use it in GraphLinksModel? Seems to me that this approach will provide me the most flexibility to accomplish my use cases. Thanks so much!

Would it be acceptable if you set { segmentIndex: -2, segmentFraction: 1, segmentOffset: new go.Point(-25, 0) } ?

If you do want to override one or more methods, there are a number of sample classes that inherit from Link.