Большое спасибо. Я решил математически рассчитать End-Edge:
Моя проблема заключалась в том, что я не смог обнаружить конечный край целевого конечного узла в отношении ссылки. Более того, существует 8 различных типов ссылок, которые определяют расположение ссылок. Итак, как сказал Томас, я должен был определить последнюю точку перед конечной точкой. Если это путь, последний узел пути - это точка перед конечной точкой. Если пути нет, начальная точка начальных узлов является последней точкой перед конечной точкой. Но если определен путь и режим соединения установлен в 1, я не могу обработать путь соединения, поскольку свойство Conn_Path содержит настраиваемую строку - но после настройки пользователь выбрал прямую ссылку (он не будет быть удаленным).
Математическое обозначение используется в качестве линейной функции y = m * x + b, и линии описываются 4 прямыми линиями, которые соответствуют краям конечного узла.
Таким образом, вы можете сделать следующий алгоритм:
Полный алгоритм использует следующий подход:
1.) Определить прямую линию между начальным и конечным узлом (есть 2 особых случая, если линия полностью горизонтальна или вертикально параллельна системе координат)
2.) Создайте прямоугольник, состоящий из четырех прямых линий (2 вертикальные / 2 горизонтальные линии)
3.) Определить пересечение первой прямой с линиями прямоугольников
4.) Исключить точку, которая не состоит из прямоугольника
5.) Определите точку на прямоугольнике с кратчайшим расстоянием => это искомая конечная точка края
Написанный код JavaScript, который я использовал для маршрутизации, выглядит следующим образом:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Erzeuge eine eigene Link-Klasse für das Routing der Pfeile, die von Hand gezogen wurden
// und über spezielle Attribute in der EAP-Datei definiert werden
// Ruft die Superklasse von go.Link im Konstruktor auf
function MultiNodePathLink() {
go.Link.call(this);
}
go.Diagram.inherit(MultiNodePathLink, go.Link); // Erben von go.Link
// ignores this.routing, this.adjusting, this.corner, this.smoothness, this.curviness
/** @override */
MultiNodePathLink.prototype.computePoints = function () {
// Die this Referenz ist hier ist ein geerbter ein go.Link. der bei Links
var startNode = this.fromNode;
var startNodeX = startNode.location.M; // X-Koordinate vom Startknoten
var startNodeY = startNode.location.N; // Y-Koordinate vom Startknoten
var endNode = this.toNode;
var endNodeX = endNode.location.M; // X-Koordinate vom Startknoten
var endNodeY = endNode.location.N; // Y-Koordinate vom Startknoten
var startNodeData = startNode.data; // Das sind die Daten
var endNodeData = endNode.data; // Das sind die Daten
// Die Link-Daten
var linkProperties = this.data;
//** Das Feld Style in [t_diagramlink] bestimmt die Connector-Darstellung **/
// http://www.capri-soft.de/blog/?p=2904
/*
* 1 = Direct Mode=1
* 2 = Auto Routing Mode=2
* 3 = Custom Line Mode=3
* 4 = Tree Vertical Mode=3;TREE=V
* 5 = Tree Horizontal Mode=3;TREE=H
* 6 = Lateral Vertical Mode=3;TREE=LV
* 7 = Lateral Horizontal Mode=3;TREE=LH
* 8 = Orthogonal Square Mode=3;TREE=OS
* 9 = Orthogonal Rounded Mode=3;TREE=OR
*/
var styleStringArray = linkProperties.style.split(";");
var mode = -1;
var tree = '';
for (var i = 0; i < styleStringArray.length; i++) {
if (styleStringArray[i].trim().indexOf('Mode=') > -1) {
mode = styleStringArray[i].replace('Mode=', '');
}
if (styleStringArray[i].trim().indexOf('TREE=') > -1) {
tree = styleStringArray[i].replace('TREE=', '');
}
}
// In der Tabelle t_diagramlinks in der Freitextspalte "Geometry" wird in einem CSV-String
// gespeichert, wie der Link letztendlich auf dem Diagram gezogen wurde
var geometryString = linkProperties.geometry.split(";");
// SX and SY are relative to the centre of the start object
var sx = geometryString[0].replace("SX=", "");
var sy = geometryString[1].replace("SY=", "");
// EX and EY are relative to the centre of the end object
var ex = geometryString[2].replace("EX=", "");
var ey = geometryString[3].replace("EY=", "");
// SX=-67;SY=-43;EX=-12;EY=-40;EDGE=3;$LLB=;
// LLT=;LMT=;LMB=CX=30:CY=13:OX=11:OY=-2:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=1:DIR=0:ROT=0;
// LRT=;LRB=;IRHS=;ILHS=;
// EDGE ranges in value from 1-4, with 1=Top, 2=Right, 3=Bottom, 4=Left (Outgoing Point of the Start Object)
var edge = geometryString[4].replace("EDGE=", "");
// Hier beginnt das Custom-Routing
this.clearPoints();
if (linkProperties.start_object_name == 'System Verification Test Reports' && linkProperties.end_object_name == 'System test specifications') {
var test = 'irrsinn';
}
// Hier werden die Wege definiert für das gecustomizte Link Routing
// Geht der Link nach oben oder unten wird die Y-Koordinate des Startknotens genutzt (Weil Orthogonales Routing)
var startConnX = null;
var startConnY = null;
if (edge == 1) { // Ecke oben
startConnX = Math.abs(startNodeX) + Math.abs((startNode.actualBounds.width / 2) + new Number(sx));
startConnY = Math.abs(startNodeY);
}
else if (edge == 3) { // Ecke unten
startConnX = Math.abs(startNodeX) + Math.abs((startNode.actualBounds.width / 2) + new Number(sx));
startConnY = Math.abs(startNodeY) + new Number(startNode.actualBounds.height);
}
else if (edge == 2) { // Ecke rechts
startConnX = Math.abs(startNodeX) + startNode.actualBounds.width;
startConnY = Math.abs(startNodeY) + Math.abs((startNode.actualBounds.height / 2) - new Number(sy));
}
else if (edge == 4) { // Ecke links
startConnX = new Number(Math.abs(startNodeX));
startConnY = Math.round(startNodeY) + Math.round((startNode.actualBounds.height / 2) - new Number(sy));
}
else {
alert('Die Edge konnte nicht entdeckt werden! Ist der Geometry String in der EAP Datei richtig?');
}
this.addPoint(new go.Point(Math.round(startConnX), Math.round(startConnY)));
// Abfrage: Gibt es einen letzten Path Punkt?
var lastPathPunkt=false;
var lastPathPunktX, lastPathPunktY;
if (mode != 1)
{
// Routing über die Zwischenwege
if (typeof linkProperties.conn_path !== "undefined" && linkProperties.conn_path !== "") {
var splittedArray = linkProperties.conn_path.split(";");
if (splittedArray.length > 1) {
// Hier ist mindestens ein Wert vorhanden da auch der erste mit Semikolon abgeschlossen wird im Path vom EA
for (var i = 0; i < splittedArray.length - 1; i++) {
var einMittelPunkt = splittedArray[i];
var mittelPunktArray = einMittelPunkt.split(":");
this.addPoint(new go.Point(Math.abs(new Number(mittelPunktArray[0])), Math.abs(new Number(mittelPunktArray[1]))))
lastPathPunktX = Math.abs(new Number(mittelPunktArray[0]));
lastPathPunktY = Math.abs(new Number(mittelPunktArray[1]));
lastPathPunkt = true;
}
}
}
}
// Wenn es keinen Pfad gab,muss der letzte Punkt mit dem Startknoten identisch sein
if (lastPathPunkt == false) {
lastPathPunktX = Math.abs(Math.round(startConnX));
lastPathPunktY = Math.abs(Math.round(startConnY));
}
// End-Routing
// Der Endpunkt in EA in Document Coordinates
var endConnX = Math.abs(endNodeX) + Math.abs((endNode.actualBounds.width / 2) + new Number(ex));
var endConnY = Math.abs(endNodeY) + Math.abs((endNode.actualBounds.height / 2) - new Number(ey));
// Spezialfälle bei horizontalen und vertikalen Linien:
if (endConnX == lastPathPunktX) {
// Es liegt eine vertikale Gerade (z.B. von oben nach unten) vor
this.addPoint(new go.Point(Math.round(lastPathPunktX), Math.round(lastPathPunktY)));
this.addPoint(new go.Point(Math.round(endConnX), Math.round(endConnY)));
} else if (endConnY == lastPathPunktY) {
// Es liegt eine horizontale Gerade (z.B. von rechts nach links) vor
this.addPoint(new go.Point(Math.round(lastPathPunktX), Math.round(lastPathPunktY)));
this.addPoint(new go.Point(Math.round(endConnX), Math.round(endConnY)));
} else {
// Es ist keine Gerade sondern ein Gerade, die mit y=m*x+b beschrieben werden kann
// 1.) Gerade zwischen Start- und Endpunkt ermittelnhn
// Ye-Ys
// m = ----- b=Ys-m*Xs oder b=Ye-m*Xe
// Xe-Xs
var m = (endConnY - lastPathPunktY) / (endConnX - lastPathPunktX);
var b = lastPathPunktY - m * lastPathPunktX
// 2.) Ermittlung der horizontalen und vertikalen Geraden des Rechteckes und dem Schnittpunkten
// Die Geraden, die das Rechteck definieren:
var rY1 = endNodeY;
var rY2 = endNodeY + endNode.actualBounds.height;
var rX1 = endNodeX;
var rX2 = endNodeX + endNode.actualBounds.width;
// (rX1, rY1) -zu-> (rX2, rY2)
// Horizontale Geraden:
// y - b
// x = -----
// m
var lengthToPoint = [];
var sX1 = (rY1 - b) / m; // S1(sX1|rY1)
if (sX1 >= rX1 && sX1 <= rX2) {
// Der Schnittpunkt sX1 ist am Rechteck
// Distanz: d=SQRT((y2-y1)^2+(x2-x1)^2)
var dS1 = Math.sqrt(Math.pow(rY1 - lastPathPunktY, 2) + Math.pow(sX1 - lastPathPunktX, 2));
lengthToPoint.push({
"distanz": dS1,
"x": sX1,
"y": rY1
});
}
var sX2 = (rY2 - b) / m; // S2(sX2|rY2)
if (sX2 >= rX1 && sX2 <= rX2) {
// Der Schnittpunkt sX2 ist am Rechteck
// Distanz: d=SQRT((y2-y1)^2+(x2-x1)^2)
var dS2 = Math.sqrt(Math.pow(rY2 - lastPathPunktY, 2) + Math.pow(sX2 - lastPathPunktX, 2));
lengthToPoint.push({
"distanz": dS2,
"x": sX2,
"y": rY2
});
}
// Vertikale Geraden:
//
// y = m*x + b
var sY1 = m * rX1 + b; // S3(rX1|sY1)
if (sY1 >= rY1 && sY1 <= rY2) {
// Der Schnittpunkt sY1 ist am Rechteck
// Distanz: d=SQRT((y2-y1)^2+(x2-x1)^2)
var dS3 = Math.sqrt(Math.pow(sY1 - lastPathPunktY, 2) + Math.pow(rX1 - lastPathPunktX, 2));
lengthToPoint.push({
"distanz": dS3,
"x": rX1,
"y": sY1
});
}
var sY2 = m * rX2 + b; // S4(rX2|sY2)
if (sY2 >= rY1 && sY2 <= rY2) {
// Der Schnittpunkt sY2 ist am Rechteck
// Distanz: d=SQRT((y2-y1)^2+(x2-x1)^2)
var dS4 = Math.sqrt(Math.pow(sY2 - lastPathPunktY, 2) + Math.pow(rX2 - lastPathPunktX, 2));
lengthToPoint.push({
"distanz": dS4,
"x": rX2,
"y": sY2
});
}
// Sortiere alle Punkte nach Distanz - der mit der kleinsten Entfernung isses
lengthToPoint.sort(function (a, b) { return a.distanz - b.distanz });
if (lengthToPoint.length > 0)
{
this.addPoint(new go.Point(Math.round(lengthToPoint[0].x), Math.round(lengthToPoint[0].y)));
}
else
{
this.addPoint(new go.Point(Math.round(lastPathPunktX), Math.round(lastPathPunktY)));
}
}
return true;
};
// end MultiNodePathLink class