Link Shifting tool not works when Spots are not set

I have non-rectangle shapes and no Spots specified for same.

In such situation diagram looks like below -

To show the arrow line linked to spots shown in red color, one need to move the shape - since link shifting tool not visible for the link.

Allowing

fromSpot: go.Spot.AllSides, toSpot: go.Spot.AllSides,

display the link shifting handles and can be changed but remain connected on rectangle border instead of shape.

Can Link Shifting tool be improved for above requirement.

Yes, I’m sure it could be improved with such a feature, but that was not the original design.

OK, try replacing this method on LinkShiftingTool:

LinkShiftingTool.prototype.updateAdornments = function(part) {
  if (part === null || !(part instanceof go.Link)) return;  // this tool only applies to Links
  var link = part;
  // show handles if link is selected, remove them if no longer selected
  var category = "LinkShiftingFrom";
  var adornment = null;
  if (link.isSelected && !this.diagram.isReadOnly) {
    var selelt = link.selectionObject;
    if (selelt !== null && link.actualBounds.isReal() && link.isVisible() &&
        selelt.actualBounds.isReal() && selelt.isVisibleObject()) {
      var spot = link.computeSpot(true);
        adornment = link.findAdornment(category);
        if (adornment === null) {
          adornment = this.makeAdornment(selelt, false);
          adornment.category = category;
          link.addAdornment(category, adornment);
        }
        adornment.elt(0).segmentFraction = link.fromSpot.isSpot() ? 1 : 0.1;
    }
  }
  if (adornment === null) link.removeAdornment(category);

  category = "LinkShiftingTo";
  adornment = null;
  if (link.isSelected && !this.diagram.isReadOnly) {
    var selelt = link.selectionObject;
    if (selelt !== null && link.actualBounds.isReal() && link.isVisible() &&
        selelt.actualBounds.isReal() && selelt.isVisibleObject()) {
      var spot = link.computeSpot(false);
        adornment = link.findAdornment(category);
        if (adornment === null) {
          adornment = this.makeAdornment(selelt, true);
          adornment.category = category;
          link.addAdornment(category, adornment);
        }
        adornment.elt(0).segmentFraction = link.toSpot.isSpot() ? 1 : 0.1;
    }
  }
  if (adornment === null) link.removeAdornment(category);
};

and replace this method of LinkShiftingTool:

LinkShiftingTool.prototype.doReshape = function(pt) {
  var ad = this._handle.part;
  var link = ad.adornedObject.part;
  var fromend = ad.category === "LinkShiftingFrom";
  var port = null;
  if (fromend) {
    port = link.fromPort;
  } else {
    port = link.toPort;
  }

  var portb = new go.Rect(port.getDocumentPoint(go.Spot.TopLeft),
                          port.getDocumentPoint(go.Spot.BottomRight));
  var lp = link.getLinkPointFromPoint(port.part, port, port.getDocumentPoint(go.Spot.Center), pt, fromend);
  var spot = new go.Spot((lp.x - portb.x) / (portb.width || 1), (lp.y - portb.y) / (portb.height || 1));
  if (fromend) {
    link.fromSpot = spot;
  } else {
    link.toSpot = spot;
  }
};

Then in your node template(s), remove any setting of fromSpot and toSpot on the port.

Hi Walter

Thanks for the updates. I think it can be improved a bit following changes as shown in fig.

The arrows should be linked at the position shown in red rectangle. (As you see in the end the line gets some angle)
Also instead of bar for repositioning, it can be a small circle which can be moved accross shape when line is selected.

Can you or your team work on this improvement.

Those kinds of minor adaptations are your responsibility.

Hi Walter,

I am able to show the adjustment handles on the line as shown in fig by changing the statement

adornment.elt(0).segmentFraction = 0;

in updateAdorment function.

The adjustment handle also moves along the line as expected.

But as you see in image, in first image, the adjustment handle is bit rotated with some angle (when line is selected).
As I move the mouse using the adjustment handle, it gets turned and after releasing displays the image as shown in last one.

With rectangular shape and orthogonal line moves around the boundary as expected.

Any help on this front when lines are straight having some angles connected to irregular shapes.

I’m sorry, but I still cannot tell what it is that does not meet your specific requirements and how you would want things differently.

What do you want to have happen when the user shifts the link connection point to the bottom left edge of the yellow “DDS” node?

You do have the source code right there, so you can make adaptations to the code to suit your needs.

Well, as you see from the image, the lines in the diagram is not looking nice, once i change using linkshifting tool.

With orthogonal, due to route calcuation, it is always straight and turns 90 deg (if required) from any kind of shape(rectangle/hexagone etc).

The expectation here is - when bottom edge is chosen then line should directly come within shape and arrow head should be shown inside the shape (with direction of arrow end towards bottom ).

As you see in image, line is shown passing though shape but it goes outside of shape and then points towards the shape bottom. Also shown powerpoint screen for same.

I am not sure, how this can achieved hence asking for you.

Modify the LinkShiftingTool constructor:

function LinkShiftingTool() {
  go.Tool.call(this);
  this.name = "LinkShifting";

  // these are archetypes for the two shift handles, one at each end of the Link:
  var h = new go.Shape();
  h.figure = "Circle";
  h.desiredSize = new go.Size(8, 8);
  h.fill = "red";
  h.stroke = null;
  h.cursor = "pointer";
  h.segmentIndex = 0;
  h.segmentOffset = new go.Point(8, 0);
  h.segmentOrientation = go.Link.OrientAlong;

  /** @type {GraphObject} */
  this._fromHandleArchetype = h;

  h = new go.Shape();
  h.figure = "Circle";
  h.desiredSize = new go.Size(8, 8);
  h.fill = "red";
  h.stroke = null;
  h.cursor = "pointer";
  h.segmentIndex = -1;
  h.segmentOffset = new go.Point(-8, 0);
  h.segmentOrientation = go.Link.OrientAlong;
  /** @type {GraphObject} */
  this._toHandleArchetype = h;

  // transient state
  /** @type {GraphObject} */
  this._handle = null;
  /** @type {List} */
  this._originalPoints = null;
}
go.Diagram.inherit(LinkShiftingTool, go.Tool);

Modify the LinkShiftingTool.updateAdornments method:

LinkShiftingTool.prototype.updateAdornments = function(part) {
  if (part === null || !(part instanceof go.Link)) return;  // this tool only applies to Links
  var link = part;
  // show handles if link is selected, remove them if no longer selected
  var category = "LinkShiftingFrom";
  var adornment = null;
  if (link.isSelected && !this.diagram.isReadOnly) {
    var selelt = link.selectionObject;
    if (selelt !== null && link.actualBounds.isReal() && link.isVisible() &&
        selelt.actualBounds.isReal() && selelt.isVisibleObject()) {
      var spot = link.computeSpot(true);
        adornment = link.findAdornment(category);
        if (adornment === null) {
          adornment = this.makeAdornment(selelt, false);
          adornment.category = category;
          link.addAdornment(category, adornment);
        }
    }
  }
  if (adornment === null) link.removeAdornment(category);

  category = "LinkShiftingTo";
  adornment = null;
  if (link.isSelected && !this.diagram.isReadOnly) {
    var selelt = link.selectionObject;
    if (selelt !== null && link.actualBounds.isReal() && link.isVisible() &&
        selelt.actualBounds.isReal() && selelt.isVisibleObject()) {
      var spot = link.computeSpot(false);
        adornment = link.findAdornment(category);
        if (adornment === null) {
          adornment = this.makeAdornment(selelt, true);
          adornment.category = category;
          link.addAdornment(category, adornment);
        }
    }
  }
  if (adornment === null) link.removeAdornment(category);
};

Modify the LinkShiftingTool.doReshape method:

LinkShiftingTool.prototype.doReshape = function(pt) {
  var ad = this._handle.part;
  var link = ad.adornedObject.part;
  var fromend = ad.category === "LinkShiftingFrom";
  var port = null;
  if (fromend) {
    port = link.fromPort;
  } else {
    port = link.toPort;
  }
  var portb = new go.Rect(port.getDocumentPoint(go.Spot.TopLeft),
    port.getDocumentPoint(go.Spot.BottomRight));
  var lp = link.getLinkPointFromPoint(port.part, port, port.getDocumentPoint(go.Spot.Center), pt, fromend);
  var spot = new go.Spot((lp.x - portb.x) / (portb.width || 1), (lp.y - portb.y) / (portb.height || 1));
  if (fromend) {
    link.fromSpot = spot;
  } else {
    link.toSpot = spot;
  }
};

Then add this custom Link class:

  function DirectLink() {
    go.Link.call(this);
  }
  go.Diagram.inherit(DirectLink, go.Link);

  DirectLink.prototype.computePoints = function() {
    var fromport = this.fromPort;
    if (fromport === null) return false;
    var toport = this.toPort;
    if (toport === null) return false;
    var fromnode = this.fromNode;
    var tonode = this.toNode;
    var fromspot = this.computeSpot(true, fromport);  // link's Spot takes precedence, if defined
    var tospot = this.computeSpot(false, toport);  // link's Spot takes precedence, if defined
    var selfloop = fromport === toport;
    var ortho = this.isOrthogonal;
    var bezier = (this.curve === Link.Bezier);
    if (!selfloop && !ortho && !bezier && this.adjusting === go.Link.None) {
      var pA = this.getLinkPoint(fromnode, fromport, fromspot, true, false, tonode, toport);  // must be newly allocated result
      var pB = this.getLinkPoint(tonode, toport, tospot, false, false, fromnode, fromport);  // must be newly allocated result
      this.clearPoints();
      this.addPoint(pA);
      this.addPoint(pB);
      return true;
    } else {
      return go.Link.prototype.computePoints.call(this);
    }
  }

And set up the LinkShiftingTool:

myDiagram.toolManager.mouseDownTools.add($(LinkShiftingTool));

And use the new DirectLink class:

    myDiagram.linkTemplate =
      $(DirectLink,
        { relinkableFrom: true, relinkableTo: true },
        $(go.Shape),
        $(go.Shape, { toArrow: "OpenTriangle" })
      );

Thanks. Now I am facing two issues -
First one
Create a link between two nodes (straight line ) and then break it in more than 2 segments.
After that trying to change one of the end point of node using LinkShiftingTool, results in last OR first two point gets changed.

I have added following code in the else part to change only the point on node which user is moving (hence added true) and works for me -

   if (!selfloop && !ortho && !bezier) {
        //var category = this.diagram.currentTool.handleCategory();
        //var currentTool = this.diagram.currentTool;
        //var category = this.diagram.currentTool._handle.part;   // This value want to access.
        //console.log(currentTool.handleCategory());
        if(true) { // (category == "LinkShiftingTo") {
            var points = this.points.toArray();
            this.clearPoints();
            for(var i = 0; i < points.length -1; i++)
                this.addPoint(points[i]);
            var pB = this.getLinkPoint(tonode, toport, tospot, false, false, fromnode, fromport);
            this.addPoint(pB);
            return true;
        }
        else if (category == "LinkShiftingFrom") {
            var points = this.points.toArray();
            this.clearPoints();
            var pA = this.getLinkPoint(fromnode, fromport, fromspot, true, false, tonode, toport);  // must be newly allocated result
            this.addPoint(pA);
            for (var i = 1; i < points.length; i++)
                this.addPoint(points[i]);
            return true;
        }
        else {
            return go.Link.prototype.computePoints.call(this);
        }
    }

But I am not able to get the handle which user is trying to move.
Added a function in LinkShiftingTool also. In debug console window, able to get the value but uncommenting the line in code gives me error and diagram doesnot get loaded at all.

How i know the handle ? And is there better approach to do above thing ?

Yes, the DirectLink class only supports a simple straight link and does not support resegmentation.

You can look at the LinkReshapingTool.handle – its GraphObject.segmentIndex and/or GraphObject.segmentFraction will give you a clue as to what is being dragged.

Trying to access the value of handle in the else part of above code.
Able to get the tool.name correctly i.e. - “LinkReshaping”.
But handle is null.

var diagram = this.diagram;
var tool = diagram.toolManager.linkReshapingTool;
var handle = tool.handle;

Is it correct place to add that code ?

Apparently not – LinkReshapingTool.handle will be non-null during the user’s dragging of the handle.

var tool = diagram.toolManager.linkReshapingTool;

If you have overridden or added a method of LinkReshapingTool, just use “this” to refer to the tool. If your method is on a different class, it does not make sense to refer to a different tool’s handle.

Do you mean I need to subclass new class from LinkReshaping ? And what method should be overriden ?

in mousedown , added LinkShiftingTool and I am trying to access the linkReshapingTool in computePoints method of DirectLink class.

Link.computePoints should not be dependent on the state of any Tool. Links can be routed at many times, not just when a tool is active.

Trying to implement another class derived from LinkReshapingTool.
CanStart method overridden and invoked call to base CanStart - which returns false.

Not sure what method needs to be overridden & and how segementation with directLink can be achieved.
Will you please guide to meet this requirement. (Segmentation with directLink)

Any info/update on my last query ?

I think you want to make your override of Link.computePoints smarter to handle additional points in the route due to reshaping and resegmentation.