Integrate link to link into dynamic ports diagram

OK, so make a copy of the DynamicPorts.html sample, and replace the definition of the Link template with the following code:

    // an orthogonal link template, reshapable and relinkable
    myDiagram.linkTemplate =
      $(CustomLink,  // defined below
        {
          routing: go.Link.AvoidsNodes,
          corner: 4,
          curve: go.Link.JumpGap,
          reshapable: true,
          resegmentable: true,
          relinkableFrom: true,
          relinkableTo: true,
          doubleClick: createJunction,
          mouseDrop: function(e, link) {
            var labelnode = null;
            e.diagram.selection.each(function(p) {
              if (p instanceof go.Node && p.category === "LinkLabel") labelnode = p;
            });
            if (labelnode !== null) attachJunction(link, labelnode, labelnode.location);
          }
        },
        new go.Binding("points").makeTwoWay(),
        $(go.Shape, { stroke: "#2F4F4F", strokeWidth: 2 })
      );

    function createJunction(e, link) { // Creates Junction node when user double clicks on a link
      e.handled = true;
      e.diagram.startTransaction("Link Label");

      var label = { category: "LinkLabel" }; // Create data for label node -- the junction Node
      e.diagram.model.addNodeData(label);

      var labelnode = e.diagram.findNodeForData(label); // Finds the created node from the NodeData
      attachJunction(link, labelnode, e.documentPoint);

      e.diagram.commitTransaction("Link Label");
    }

    function attachJunction(link, labelnode, pos) {
      labelnode.labeledLink = link;		// set the labeledLink for the junction node

      var index = link.findClosestSegment(pos); // Finds the index of the segment that is closest to POS
      labelnode.segmentIndex = index;

      var startPoint = link.points.elt(index); // Grabs the point at the start and end of the segment
      var endPoint = link.points.elt(index + 1);
      // Finds diff between pos and startPoint divided by diff of endPoint and startPoint
      if (Math.abs(startPoint.x - endPoint.x) < 0.1) { // Segment is horizontal
        labelnode.segmentFraction = (pos.y - startPoint.y) / (endPoint.y - startPoint.y);
      } else { // Segment is vertical
        labelnode.segmentFraction = (pos.x - startPoint.x) / (endPoint.x - startPoint.x);
      }
    }

    // This is the template for a label node on a link.
    // This node supports user-drawn links to and from the label node.
    // It is actually larger than it appears, so it can be selected and deleted.
    myDiagram.nodeTemplateMap.add("LinkLabel",
      $(go.Node,
        {
          layerName: "Foreground",  // always have link label nodes in front of Links
          background: "transparent",
          width: 12, height: 12,
          locationSpot: go.Spot.Center,
          movable: false,  // but becomes movable when it's not a label on a Link
          deletable: false,  // but becomes deletable when there's no connected link
          // only deletable when it's unconnected
          linkConnected: function(node, link, port) {
            node.deletable = false;
          },
          linkDisconnected: function(node, link, port) {
            node.deletable = (node.linksConnected.count === 0);
          }
        },
        // only movable when it's a label Node of a Link
        new go.Binding("movable", "labeledLink", function(link) { return link === null; }).ofObject(),
        new go.Binding("segmentIndex").makeTwoWay(),  // remember where this label belongs on the link
        new go.Binding("segmentFraction").makeTwoWay(),
        $(go.Shape, "Circle",
          { // a junction node appears as just a small black circle
            position: new go.Point(3, 3),  // center the circle in the Node
            width: 6, height: 6,
            fill: "blue", strokeWidth: 0,
            portId: "",
            fromLinkable: true, toLinkable: true, cursor: "pointer"
          })
      ));

This adds a “LinkLabel” template that is a junction node. Double click on an orthogonal link to add a new junction node as a label on the link.

But the CustomLink class needs changes to handle the new kind of node that now can exist in the diagram. So update these overrides:

  /** @override */
  CustomLink.prototype.computeEndSegmentLength = function(node, port, spot, from) {
    var esl = go.Link.prototype.computeEndSegmentLength.call(this, node, port, spot, from);
    var other = this.getOtherPort(port);
    if (port !== null && other !== null && node.category !== "LinkLabel" && other.part.category !== "LinkLabel") {
      var thispt = port.getDocumentPoint(this.computeSpot(from));
      var otherpt = other.getDocumentPoint(this.computeSpot(!from));
      if (Math.abs(thispt.x - otherpt.x) > 20 || Math.abs(thispt.y - otherpt.y) > 20) {
        var info = this.findSidePortIndexAndCount(node, port);
        var idx = info[0];
        var count = info[1];
        if (port._side == "top" || port._side == "bottom") {
          if (otherpt.x < thispt.x) {
            return esl + 4 + idx * 8;
          } else {
            return esl + (count - idx - 1) * 8;
          }
        } else {  // left or right
          if (otherpt.y < thispt.y) {
            return esl + 4 + idx * 8;
          } else {
            return esl + (count - idx - 1) * 8;
          }
        }
      }
    }
    return esl;
  };

  /** @override */
  CustomLink.prototype.hasCurviness = function() {
    if (isNaN(this.curviness)) return true;
    return go.Link.prototype.hasCurviness.call(this);
  };

  /** @override */
  CustomLink.prototype.computeCurviness = function() {
    if (isNaN(this.curviness)) {
      var fromnode = this.fromNode;
      var tonode = this.toNode;
      if (fromnode.category !== "LinkLabel" && tonode.category !== "LinkLabel") {
        var fromport = this.fromPort;
        var fromspot = this.computeSpot(true);
        var frompt = fromport.getDocumentPoint(fromspot);
        var toport = this.toPort;
        var tospot = this.computeSpot(false);
        var topt = toport.getDocumentPoint(tospot);
        if (Math.abs(frompt.x - topt.x) > 20 || Math.abs(frompt.y - topt.y) > 20) {
          if ((fromspot.equals(go.Spot.Left) || fromspot.equals(go.Spot.Right)) &&
              (tospot.equals(go.Spot.Left) || tospot.equals(go.Spot.Right))) {
            var fromseglen = this.computeEndSegmentLength(fromnode, fromport, fromspot, true);
            var toseglen = this.computeEndSegmentLength(tonode, toport, tospot, false);
            var c = (fromseglen - toseglen) / 2;
            if (frompt.x + fromseglen >= topt.x - toseglen) {
              if (frompt.y < topt.y) return c;
              if (frompt.y > topt.y) return -c;
            }
          } else if ((fromspot.equals(go.Spot.Top) || fromspot.equals(go.Spot.Bottom)) &&
                     (tospot.equals(go.Spot.Top) || tospot.equals(go.Spot.Bottom))) {
            var fromseglen = this.computeEndSegmentLength(fromnode, fromport, fromspot, true);
            var toseglen = this.computeEndSegmentLength(tonode, toport, tospot, false);
            var c = (fromseglen - toseglen) / 2;
            if (frompt.x + fromseglen >= topt.x - toseglen) {
              if (frompt.y < topt.y) return c;
              if (frompt.y > topt.y) return -c;
            }
          }
        }
      }
    }
    return go.Link.prototype.computeCurviness.call(this);
  };

  CustomLink.prototype.getLinkPoint = function(node, port, spot, from, ortho, othernode, otherport, result) {
    if (ortho && node.category === "LinkLabel") {
      var link = node.labeledLink;
      if (link) {
        var idx = node.segmentIndex;
        var p1 = link.getPoint(idx);
        var p2 = link.getPoint(idx + 1);
        var othpt = otherport.getDocumentPoint(go.Spot.Center);
        if (Math.abs(p1.x - p2.x) < 0.1) {
          return port.getDocumentPoint((othpt.x > p1.x) ? go.Spot.Right : go.Spot.Left, result);
        } else {
          return port.getDocumentPoint((othpt.y > p1.y) ? go.Spot.Bottom : go.Spot.Top, result);
        }
      }
    }
    return go.Link.prototype.getLinkPoint.call(this, node, port, spot, from, ortho, othernode, otherport, result);
  }

Basically the changes are only executing the old CustomLink code when the nodes are not junction nodes (i.e. category is “LinkLabel”), plus a new override for Link.getLinkPoint.