Integrate link to link into dynamic ports diagram


#1

I want to integrate link to link into dynamic ports diagram. Can you please provide any help/reference in related to my query?

https://gojs.net/latest/samples/linksToLinks.html

https://gojs.net/latest/samples/dynamicPorts.html


#2

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.


#3

Thanks for your reply.

As i have integrated this in to my system and it is working fine on double click on any point on link.

Can it be work with single click like there will be no need to double click, just make link from any point to any point?


#4

What changes do you think you would have to make to have a click rather than a double click on a link be what creates a junction label node? What have you tried? Have you read https://gojs.net/learn and then all of the relevant pages of the Introduction, starting at https://gojs.net/intro?


#5

I am going with double click functionality now but when i load json in dynamic ports diagram with links to links enabled then dots are not on the proper place on the links .

Example -

Screenshot_6


#6

What information is being saved for each junction node (a.k.a. label node)?


#7

{ “class”: “go.GraphLinksModel”,
“copiesArrays”: true,
“copiesArrayObjects”: true,
“linkFromPortIdProperty”: “fromPort”,
“linkToPortIdProperty”: “toPort”,
“nodeDataArray”: [
{“key”:1, “uniquekey”:“1-3-1”, “image”:“http://beta.brstdev.com/Cable/wp-content/uploads/2018/10/list3.png”, “label”:“A”, “showname”:“DC1F\n4 contact”, “name”:“DC1F\n4 contact\nA”, “leftArray”:[], “topArray”:[], “bottomArray”:[], “rightArray”:[ {“portColor”:"#000000", “portId”:“right0”},{“portColor”:"#000000", “portId”:“right1”},{“portColor”:"#000000", “portId”:“right2”},{“portColor”:"#000000", “portId”:“right3”} ], “loc”:"-182 45"},
{“key”:-4, “uniquekey”:“1-3-3”, “image”:“http://beta.brstdev.com/Cable/wp-content/uploads/2018/10/list2.png”, “label”:“B”, “showname”:“BH1M\n6 contact”, “name”:“BH1M\n6 contact\nB”, “leftArray”:[ {“portColor”:"#000000", “portId”:“left0”},{“portColor”:"#000000", “portId”:“left1”},{“portColor”:"#000000", “portId”:“left2”},{“portColor”:"#000000", “portId”:“left3”},{“portColor”:"#000000", “portId”:“left4”},{“portColor”:"#000000", “portId”:“left5”} ], “topArray”:[], “bottomArray”:[], “rightArray”:[], “loc”:“409.99999999999994 -6”},
{“key”:3, “uniquekey”:“1-3-2”, “image”:“http://beta.brstdev.com/Cable/wp-content/uploads/2018/10/list2.png”, “label”:“C”, “showname”:“BH1M\n6 contact”, “name”:“BH1M\n6 contact\nC”, “leftArray”:[ {“portColor”:"#000000", “portId”:“left0”},{“portColor”:"#000000", “portId”:“left1”},{“portColor”:"#000000", “portId”:“left2”},{“portColor”:"#000000", “portId”:“left3”},{“portColor”:"#000000", “portId”:“left4”},{“portColor”:"#000000", “portId”:“left5”} ], “topArray”:[], “bottomArray”:[], “rightArray”:[], “loc”:“121 351.9999999999999”},
{“key”:-5, “uniquekey”:“1-3-4”, “image”:“http://beta.brstdev.com/Cable/wp-content/uploads/2018/10/list3.png”, “label”:“D”, “showname”:“DC1F\n4 contact”, “name”:“DC1F\n4 contact\nD”, “leftArray”:[ {“portColor”:"#000000", “portId”:“left0”},{“portColor”:"#000000", “portId”:“left1”},{“portColor”:"#000000", “portId”:“left2”},{“portColor”:"#000000", “portId”:“left3”} ], “topArray”:[], “bottomArray”:[], “rightArray”:[], “loc”:“467.99999999999994 379.9999999999999”},
{“category”:“LinkLabel”, “key”:-6, “segmentIndex”:3, “segmentFraction”:0.7517857142857143},
{“category”:“LinkLabel”, “key”:-7, “segmentIndex”:2, “segmentFraction”:0.5961538461538461}
],
“linkDataArray”: [
{“from”:1, “to”:-4, “fromPort”:“right0”, “toPort”:“left2”, “points”:[-96,18,-82,18,120,18,120,-15,290,-15,324,-15]},
{“from”:1, “to”:-5, “fromPort”:“right1”, “toPort”:“left1”, “points”:[-96,36,-70,36,-68,36,-68,36,212,36,212,371,360,371,382,371]},
{“from”:1, “to”:3, “fromPort”:“right2”, “toPort”:“left4”, “points”:[-96,54,-78,54,-28.5,54,-28.5,379,-11,379,35,379]},
{“from”:-7, “to”:-6, “fromPort”:"", “toPort”:"", “points”:[-25.5,247.75,-25.5,237.75,-25.5,159.375,142.5,159.375,142.5,49,142.5,39]}
]}


#8

Notice that there is no information in the model associating links with label nodes. And notice what the Links to Links sample does: it sets GraphLinksModel.linkLabelKeysProperty.

For example:

    myDiagram.model =
      $(go.GraphLinksModel,
        {
          linkLabelKeysProperty: "labelKeys",
          nodeDataArray:
            [ . . .

Or in the JSON-formatted text, add this line:

  "linkLabelKeysProperty": "labelKeys",

and for any links that have label nodes, add a property like this:

{"from":1, "to":2, "labelKeys":[ 5 ]},

#9

What will be the value of labelkeys?


#10

I have this type of linkdataarray -

{“key”: “1-3-1-5”, “from”: 1, “to”: 3, “fromPort”: “right0”, “toPort”: “left0”, “right”: 1, “left”: 1}


#11

https://gojs.net/latest/api/symbols/GraphLinksModel.html#linkLabelKeysProperty
Basically, an Array of node keys.


#12

Okay but for link that has been created from middle of one link to another has empty labelKeys but others fills automatically and that giving error while editing json

“from”:-6, “to”:-4, “labelKeys”:[], “fromPort”:"", “toPort”:""

Error -

Uncaught TypeError: Cannot read property ‘x’ of undefined
at CustomLink.getLinkPoint ((index):1103)
at CustomLink.W.computePoints (go.js:1557)
at CustomLink.W.updateRoute.W.hn (go.js:1554)
at CustomLink. (go.js:1539)
at CustomLink.g.np (go.js:1119)
at fj (go.js:978)
at E.BA (go.js:761)
at yk (go.js:760)
at $k (go.js:900)
at Wh (go.js:757)


#13

I cannot reproduce that problem. When I edit to get this:
image

The resulting model is:

{ "class": "GraphLinksModel",
  "linkLabelKeysProperty": "labelKeys",
  "nodeDataArray": [ 
{"key":1, "text":"Alpha", "color":"lightblue", "loc":"0 0"},
{"key":2, "text":"Beta", "color":"orange", "loc":"100, -50"},
{"key":3, "text":"Gamma", "color":"lightgreen", "loc":"-52.00000000000003 58"},
{"key":4, "text":"Delta", "color":"pink", "loc":"128.99999999999991 73"},
{"key":5, "category":"LinkLabel", "segmentIndex":2, "segmentFraction":0.4},
{"category":"LinkLabel", "key":-6, "segmentIndex":2, "segmentFraction":0.8466665649414062},
{"category":"LinkLabel", "key":-7, "segmentIndex":3, "segmentFraction":0.24946229996219757},
{"category":"LinkLabel", "key":-8, "segmentIndex":1, "segmentFraction":0.7237442225626071},
{"category":"LinkLabel", "key":-9, "segmentIndex":2, "segmentFraction":0.4718813758397271}
 ],
  "linkDataArray": [ 
{"from":1, "to":2, "labelKeys":[ 5 ]},
{"from":5, "to":4, "labelKeys":[ -6 ]},
{"from":3, "to":4, "labelKeys":[ -7,-8 ]},
{"from":-6, "to":-7, "labelKeys":[ -9 ]},
{"from":-9, "to":-8, "labelKeys":[]}
 ]}

Certainly any Link that does not have any label Nodes can have the “labelKeys” property be an empty Array.