Thanks. That helped pointing me in the right direction. I had already started trying to override the makeGeometry in a different way (not relying on the super method) but I was trying to use an Arc segment. After seeing your example, using QuadraticBezier segments made it much simpler.
However, although I get a perfect result for the diagrams above, the moment I start moving the bend points around I get a weird behavior, with which I could use your help with. It’s not just when moving, it happens the same if the bend points started in other positions.
So, first the code:
import * as go from 'gojs';
class Vector {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x
this.y = y
}
sub(vector: Vector): Vector {
return new Vector(this.x - vector.x, this.y - vector.y);
}
add(vector: Vector): Vector {
return new Vector(this.x + vector.x, this.y + vector.y);
}
scale(n: number): Vector {
return new Vector(this.x * n, this.y * n);
}
length(): number {
return Math.sqrt(this.x ** 2 + this.y ** 2);
}
normal(): Vector {
const length = this.length();
return length ? new Vector(this.x / length, this.y / length) : new Vector(0, 0);
}
}
export class RoundedCornersLink extends go.Link {
/**
* A line from vecFrom to vecTo
*/
private line(vecFrom: Vector, vecTo: Vector): Vector[] {
let halfVec = vecFrom.add(vecTo.sub(vecFrom).scale(.5));
return [halfVec, vecTo];
}
// Adds 'n' units to vecFrom pointing in direction vecTo
private vecDir(vecFrom: Vector, vecTo: Vector, n: number) {
return vecFrom.add(vecTo.sub(vecFrom).normal().scale(n));
}
// Draws a line, but skips 'r' units from the begin and end
private lineR(vecFrom: Vector, vecTo: Vector, r: number) {
let vec = vecTo.sub(vecFrom).normal().scale(r);
return this.line(vecFrom.add(vec), vecTo.sub(vec));
}
// An edge in vecFrom, to vecTo with radius r
private edge(vecFrom: Vector, vecTo: Vector, r: number) {
let v = this.vecDir(vecFrom, vecTo, r);
return [vecFrom, v];
}
public makeGeometry(): go.Geometry {
if (this.pointsCount <= 2) {
return super.makeGeometry();
} else {
// const superGeo = super.makeGeometry(); // console.log((superGeo as any).flattenedSegments[0]);
// Start by creating the PathFigure with the desired start point
const startPoint = this.points.first();
const pathFigure = new go.PathFigure(startPoint.x, startPoint.y, false);
// For each middle point, add a line segment from the previous point to a few "pixels" short of the next point;
// Then add a curvature (QuadraticBezier) in the direction of the next point
for (let i = 1; i < this.pointsCount - 1; i ++) {
const vectorFrom = new Vector(this.getPoint(i - 1).x, this.getPoint(i - 1).y);
const vectorTo = new Vector(this.getPoint(i).x, this.getPoint(i).y);
const vectorNext = new Vector(this.getPoint(i + 1).x, this.getPoint(i + 1).y);
const line = this.lineR(vectorFrom, vectorTo, this.corner);
const edge = this.edge(vectorTo, vectorNext, this.corner);
pathFigure.add(new go.PathSegment(go.PathSegment.Line, line[1].x, line[1].y));
pathFigure.add(new go.PathSegment(go.PathSegment.QuadraticBezier, edge[1].x, edge[1].y, edge[0].x, edge[0].y));
}
// Finish by adding the end point to the PathFigure
const endPoint = this.points.last();
pathFigure.add(new go.PathSegment(go.PathSegment.Line, endPoint.x,endPoint.y));
const geometry = new go.Geometry();
geometry.add(pathFigure);
geometry.normalize(); // console.log((geometry as any).flattenedSegments[0])
return geometry;
}
}
/**
* Constructs the link's route by modifying {@link #points}.
* @return {boolean} true if it computed a route of points
*/
public computePoints(): boolean {
if (!this.data.$bendPoints?.length) {
return super.computePoints();
} else {
const firstBendPoint = new go.Point(this.data.$bendPoints[0].x, this.data.$bendPoints[0].y);
const lastBendPoint = new go.Point(this.data.$bendPoints[this.data.$bendPoints.length - 1].x, this.data.$bendPoints[this.data.$bendPoints.length - 1].y);
const startPoint = this.getLinkPointFromPoint(this.fromNode, this.fromPort, this.fromPort.getDocumentPoint(go.Spot.Center), firstBendPoint, true);
const lastPoint = this.getLinkPointFromPoint(this.toNode, this.toPort, this.toPort.getDocumentPoint(go.Spot.Center), lastBendPoint, false);
this.clearPoints();
this.addPoint(startPoint);
for (const bendPoint of this.data.$bendPoints) {
this.addPointAt(bendPoint.x, bendPoint.y);
}
this.addPoint(lastPoint);
return true;
}
}
}
this is the relevant linkTemplate section:
{
curve: go.Link.JumpOver,
corner: 20,
reshapable: true,
resegmentable: true,
zOrder: 1,
},
and here’s the result:
Pretty good! But now I tried moving a link point and things started getting weird (note: I only dragged the middle point of the link):
Notice that the start and end points are not where they should be…Another weird behavior I noticed, is that, if I call the super method (but just ignore the result and build my own geometry), this happens:
What other wizardries is the super method doing that affects the link selection adornment?
The reason why I didn’t follow your tip to first call the super method and then try to modify the segments is because the resulting super geometry is normalized, so the start point is 0,0, which means a whole lot of other calculations. And although we’re essentially doing the same thing (adding a line segment followed by a QuadraticBezier segment), your math to get to the points seems a bit more complicated (you have angles and rotations that I don’t quite follow…)
Any idea why the start and end points don’t land where they should?