Hi, I have created short demo where it is going wrong:
<!DOCTYPE html>
<html>
<head>
<title>Asynchronous AvoidsNodes Routing</title>
<!-- Copyright 1998-2021 by Northwoods Software Corporation. -->
<meta name="description" content="Asynchronously route a few Orthogonal Links to be AvoidsNodes routing, to speed up initial rendering of large diagrams">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://unpkg.com/gojs"></script>
<script id="code">
// Support for asychronously routing links or JumpOver or JumpGap curve
// To set Link.routing = go.Link.AvoidsNodes after "InitialLayoutCompleted":
// 1: set Link.routing = go.Link.Orthogonal in your link templates
// 2: create an instance of this class for your diagram:
// var diag = ...;
// var delay = new DelayLinkRoutingCurve(diag);
//
// To also set Link.curve = go.Link.JumpOver (or JumpGap):
// 1: set Link.curve = go.Link.None in your link templates
// 2: create an instance of this class for your diagram:
// var diag = ...;
// var delay = new DelayLinkRoutingCurve(diag, go.Link.JumpOver);
//
// To set Link.curve = go.Link.JumpOver or JumpGap without setting routing = go.Link.AvoidsNodes:
// 1: set Link.curve = go.Link.None and Link.routing = go.Link.Orthogonal in your link templates
// 2: create an instance of this class for your diagram:
// var diag = ...;
// var delay = new DelayLinkRoutingCurve(diag, go.Link.JumpOver, go.Link.Orthogonal);
// If you no longer need a DelayLinkRoutingCurve, set its .diagram = null.
// This will remove the listeners that it had registered on the Diagram.
// Two approaches for incrementally routing links with AvoidsNodes routing,
// assuming links initially have Orthogonal routing:
// 1: After the model is loaded and the initial layout has happened,
// start asynchronously routing a few links at a time until there are none left.
// 2: When the user scrolls, make sure any links that might be shown
// in the viewport are routed first.
function DelayLinkRoutingCurve(diagram, curve, routing) {
if (!curve) curve = go.Link.None;
if (!routing) routing = go.Link.AvoidsNodes;
this._diagram = null;
this._routing = routing;
this._curve = curve;
this._batch = 10;
this._AllOrthos = new go.Set(); // all links that need to be routed
this._ViewOrthos = new go.Set(); // just those links found in or crossing the current viewport
this._ViewportBounds = new go.Rect(); // last viewport bounds
this._ilc = this.startAvoiding.bind(this);
this._vbc = this.updateLinksInViewport.bind(this);
this._am = this.avoidMore.bind(this);
this._jm = this.jumpMore.bind(this);
if (diagram) this.diagram = diagram;
}
// default value: null
Object.defineProperty(DelayLinkRoutingCurve.prototype, "diagram",
{
get: function() { return this._diagram; },
set: function(val) {
var old = this._diagram;
if (val !== old) {
if (old) {
old.removeDiagramListener("InitialLayoutCompleted", this._ilc);
old.removeDiagramListener("ViewportBoundsChanged", this._vbc);
this._AllOrthos.clear();
}
this._diagram = val;
if (val) {
val.addDiagramListener("InitialLayoutCompleted", this._ilc);
val.addDiagramListener("ViewportBoundsChanged", this._vbc);
}
}
}
});
// default value: go.Link.AvoidsNodes
Object.defineProperty(DelayLinkRoutingCurve.prototype, "routing",
{
get: function() { return this._routing; },
set: function(val) { this._routing = val; }
});
// default value: go.Link.None
Object.defineProperty(DelayLinkRoutingCurve.prototype, "curve",
{
get: function() { return this._curve; },
set: function(val) { this._curve = val; }
});
// default value: 10
Object.defineProperty(DelayLinkRoutingCurve.prototype, "batch",
{
get: function() { return this._batch; },
set: function(val) { this._batch = val; }
});
DelayLinkRoutingCurve.prototype.startAvoiding = function() {
var delay = this;
var remaining = 0;
if (delay.routing !== go.Link.None && delay.routing !== go.Link.Orthogonal) {
delay._AllOrthos.clear();
delay.diagram.links.each(function(l) {
if (l.routing === go.Link.Orthogonal) delay._AllOrthos.add(l);
});
delay.updateLinksInViewport();
remaining = delay._AllOrthos.count;
// to improve initial responsiveness, don't AvoidsNodes route any links until after a delay
setTimeout(delay._am, 10);
} else if (delay.curve !== go.Link.None && delay.curve !== go.Link.Bezier) {
remaining = 1;
setTimeout(delay._jm, 10);
}
delay.onUpdate(remaining);
}
DelayLinkRoutingCurve.prototype.updateLinksInViewport = function() {
var delay = this;
// don't search again in viewport if viewport bounds hasn't changed
if (delay._ViewportBounds.equals(delay.diagram.viewportBounds)) return;
if (delay.routing !== go.Link.None && delay.routing !== go.Link.Orthogonal) {
setTimeout(function() {
delay._ViewportBounds.set(delay.diagram.viewportBounds);
var links = delay.diagram.findObjectsIn(delay._ViewportBounds,
function(x) { var p = x.part; return (p instanceof go.Link) ? p : null; },
null,
true);
// make Orthogonal Links in the viewport take routing precedence over Links that are elsewhere
delay._ViewOrthos.clear();
links.each(function(l) {
if (l.routing === go.Link.Orthogonal) delay._ViewOrthos.add(l);
});
}, 10);
}
}
DelayLinkRoutingCurve.prototype.avoidMore = function() {
var delay = this;
if (delay.diagram.currentTool !== delay.diagram.defaultTool) {
setTimeout(delay._am, 1000); // process events for tools
} else {
// route one or more links, if any need it
if (delay._AllOrthos.count > 0) {
delay.diagram.commit(function(diag) {
for (var i = 0; i < delay._batch; i++) { // could be smarter and do more in each batch if they are quick enough
var link = delay._ViewOrthos.first(); // first route links in view
if (!link) link = delay._AllOrthos.first(); // otherwise pick another link to route
if (!link) break; // all done?
link.routing = delay.routing;
delay._AllOrthos.remove(link);
delay._ViewOrthos.remove(link);
}
}, null); // skipsUndoManager
}
// if there are any remaining, do them later
var remaining = delay._AllOrthos.count;
if (remaining > 0) {
setTimeout(delay._am, 10);
} else if (delay.curve !== go.Link.None && delay.curve !== go.Link.Bezier) {
remaining = 1;
setTimeout(delay._jm, 10);
}
delay.onUpdate(remaining);
}
}
DelayLinkRoutingCurve.prototype.jumpMore = function() {
var delay = this;
delay.diagram.commit(function(diag) {
diag.links.each(function(l) {
if (l.curve === go.Link.None && l.isOrthogonal) l.curve = delay.curve;
});
}, null); // skipsUndoManager
delay.onUpdate(0);
}
DelayLinkRoutingCurve.prototype.onUpdate = function(numremaining) { }
// end of DelayLinkRoutingCurve class
function init() {
var $ = go.GraphObject.make;
myDiagram =
$(go.Diagram, "myDiagramDiv",
{
layout: $(go.TreeLayout,{
layerSpacing: 60, // Spacing between the Layers
nodeSpacing: 60, // Spacing between the Columns
angle: 90, // value can be 0(left to right)/90(top to bottom)/180(right to left)/270(bottom to top)
//alignment: go.TreeLayout.AlignmentStart,
// layerStyle: go.TreeLayout.LayerSiblings,
// arrangement: go.TreeLayout.ArrangementVertical,
// compaction: go.TreeLayout.CompactionBlock,
setsChildPortSpot: false,
setsPortSpot: false,
}),
model: $(go.GraphLinksModel, {
linkKeyProperty: 'key', // IMPORTANT! must be defined for merges and data sync when using GraphLinksModel
}),
"InitialLayoutCompleted": function(e) {
// scroll to where there are some nodes
e.diagram.select(e.diagram.nodes.first());
e.diagram.commandHandler.scrollToPart(e.diagram.nodes.first());
},
"animationManager.isEnabled": false
});
// using DelayLinkRoutingCurve:
var delay = new DelayLinkRoutingCurve(myDiagram, go.Link.JumpOver);
// FOR DEBUGGING in this sample only: just to show how many links still need to have routing AvoidsNodes
delay.onUpdate = function(numremaining) {
document.getElementById("myInfo").textContent = this._ViewOrthos.count.toString() + " of " + numremaining;
};
myDiagram.nodeTemplate =
$(go.Node, "Auto",{
toSpot: go.Spot.NotBottomSide,
fromSpot: go.Spot.NotTopSide,
portSpreading: go.Node.SpreadingNone,
},
$(go.Shape, { fill: "white" },
new go.Binding("fill")),
$(go.TextBlock, { margin: new go.Margin(18, 28) },
new go.Binding("text"))
);
myDiagram.linkTemplate =
$(go.Link,
{ routing: go.Link.Orthogonal, corner: 10 }, // NOTE: Orthogonal, not AvoidsNodes
{
mouseEnter: function(e, link) { link.path.stroke = "red"; link.path.strokeWidth = 3; },
mouseLeave: function(e, link) { link.path.stroke = "black"; link.path.strokeWidth = 1.5; }
},
$(go.Shape, { strokeWidth: 1.5 })
);
// set up initial model
test();
}
function test() {
generate(1500, 2, 6);
}
// create a model that is overall somewhat tree-structured but includes many non-tree-structured links
function generate(numNodes, minChil, maxChil) {
var nodeArray = [];
var linkArray = [];
for (var i = 0; i < numNodes; i++) {
nodeArray.push({
key: i, // the unique identifier
text: i.toString(), // some text to be shown by the node template
fill: go.Brush.randomColor() // a color to be shown by the node template
});
}
// Randomize the node data
for (i = 0; i < nodeArray.length; i++) {
var swap = Math.floor(Math.random() * nodeArray.length);
var temp = nodeArray[swap];
nodeArray[swap] = nodeArray[i];
nodeArray[i] = temp;
}
// Takes the random collection of node data and creates a random tree with them.
// Respects the minimum and maximum number of links from each node.
// The minimum can be disregarded if we run out of nodes to link to.
if (nodeArray.length > 1) {
// keep the Set of node data that do not yet have a parent
var available = new go.Set();
available.addAll(nodeArray);
for (var i = 0; i < nodeArray.length; i++) {
var parent = nodeArray[i];
available.remove(parent);
// assign some number of node data as children of this parent node data
var children = Math.floor(Math.random() * (maxChil - minChil + 1)) + minChil;
for (var j = 0; j < children; j++) {
var child = available.first();
if (child === null) break; // oops, ran out already
available.remove(child);
linkArray.push({ from: parent.key, to: child.key });
}
if (available.count === 0) break; // nothing left?
}
// add some extra non-tree links
for (var i = 0; i < numNodes/2; i++) {
linkArray.push({
from: nodeArray[Math.floor(Math.random()*numNodes)].key,
to: nodeArray[Math.floor(Math.random()*numNodes)].key
});
}
}
myDiagram.model = new go.GraphLinksModel([
{key: -1, text: "START"}
,{key: "md_52",text:"Decision"}
,{key: "1_53", text: "1"}
,{key: "2_54", text: "2"}
,{key: "1_55", text: "1"}
,{key: "2_56", text: "2"}
,{key: "1_57", text: "1"}
,{key: "2_58", text: "2"}
,{key: "1_59", text: "1"}
,{key: "2_60", text: "2"}
,{key: "-2_61", text: "END"}
,{key: "3_62", text: "3"}
,{key: "4_63", text: "4"}
,{key: "5_64", text: "5"}
,{key: "4_65", text: "4"}
,{key: "1_66", text: "1"}
,{key: "-2_67", text: "END"}
,{key: "6_68", text: "6"}
,{key: "-2_69", text: "END"}
,{key: "-2_70", text: "END"}
,{key: "7_71", text: "7"}
,{key: "md_73", text: "Decision"}
,{key: "8_74", text: "8"}
,{key: "7_75", text: "7"}
,{key: "-2_76", text: "END"}
,{key: "9_77", text: "9"}
,{key: "7_78", text: "7"}
,{key: "9_79", text: "9"}
,{key: "-2_80", text: "END"}
,{key: "10_81", text: "10"}
,{key: "1_82", text: "1"}
,{key: "10_83", text: "10"}
,{key: "1_84", text: "1"}
,{key: "10_85", text: "10"}
,{key: "-2_86", text: "END"}
,{key: "11_87", text: "11"}
,{key: "4_88", text: "4"}
,{key: "5_89", text: "5"}
,{key: "12_90", text: "12"}
,{key: "1_91", text: "1"}
,{key: "-2_92", text: "END"}
],
[
{from: -1, to: "md_52"}
,{from: "md_52", to: "1_53"}
,{from: "1_53", to: "2_54"}
,{from: "2_54", to: "1_55"}
,{from: "1_55", to: "2_56"}
,{from: "2_56", to: "1_57"}
,{from: "1_57", to: "2_58"}
,{from: "2_58", to: "1_59"}
,{from: "1_59", to: "2_60"}
,{from: "2_60", to: "-2_61"}
,{from: "md_52", to: "3_62"}
,{from: "3_62", to: "4_63"}
,{from: "4_63", to: "5_64"}
,{from: "5_64", to: "4_65"}
,{from: "4_65", to: "1_66"}
,{from: "1_66", to: "-2_67"}
,{from: "md_52", to: "6_68"}
,{from: "6_68", to: "-2_69"}
,{from: "-2_69", to: "-2_70"}
,{from: "md_52", to: "7_71"}
,{from: "7_71", to: "md_73"}
,{from: "md_73", to: "8_74"}
,{from: "8_74", to: "7_75"}
,{from: "7_75", to: "-2_76"}
,{from: "md_73", to: "9_77"}
,{from: "9_77", to: "7_78"}
,{from: "7_78", to: "9_79"}
,{from: "9_79", to: "-2_80"}
,{from: "md_52", to: "10_81"}
,{from: "10_81", to: "1_82"}
,{from: "1_82", to: "10_83"}
,{from: "10_83", to: "1_84"}
,{from: "1_84", to: "10_85"}
,{from: "10_85", to: "-2_86"}
,{from: "md_52", to: "11_87"}
,{from: "11_87", to: "4_88"}
,{from: "4_88", to: "5_89"}
,{from: "5_89", to: "12_90"}
,{from: "12_90", to: "1_91"}
,{from: "1_91", to: "-2_92"}
]);
}
</script>
</head>
<body onload="init()">
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:900px"></div>
<button onclick="test()">Generate new model</button>
<span id="myInfo"></span>
</body>
</html>
Have highlighted where links are not properly plotted:
All the links must have came from top right…?