forked from NorthwoodsSoftware/GoJS
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmultiNodePathLinks.html
More file actions
204 lines (190 loc) · 8.02 KB
/
multiNodePathLinks.html
File metadata and controls
204 lines (190 loc) · 8.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
<!DOCTYPE html>
<html>
<head>
<title>Multi-Node Path Links</title>
<meta name="description" content="A custom Link routing that goes smoothly through a sequence of Nodes." />
<!-- Copyright 1998-2016 by Northwoods Software Corporation. -->
<meta charset="UTF-8">
<script src="go.js"></script>
<link href="../assets/css/goSamples.css" rel="stylesheet" type="text/css" /> <!-- you don't need to use this -->
<script src="goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
<script id="code">
function init() {
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
var $ = go.GraphObject.make;
myDiagram =
$(go.Diagram, "myDiagramDiv", // the ID of the DIV HTML element
{
initialContentAlignment: go.Spot.Center,
"Changed": invalidateLinkRoutes,
"undoManager.isEnabled": true
});
myDiagram.nodeTemplate =
$(go.Node, go.Panel.Auto,
{ locationSpot: go.Spot.Center },
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape,
{ figure: "Circle", fill: "white" },
new go.Binding("fill", "color")),
$(go.TextBlock,
{ font: "bold 11pt sans-serif" },
new go.Binding("text"))
);
myDiagram.linkTemplate =
$(MultiNodePathLink, // subclass of Link, defined below
go.Link.Bezier,
{ layerName: "Background", toShortLength: 4 },
$(go.Shape, { strokeWidth: 4 },
new go.Binding("stroke", "color")),
$(go.Shape, { toArrow: "Standard", scale: 3, strokeWidth: 0 },
new go.Binding("fill", "color"))
);
function invalidateLinkRoutes(e) {
// when a Node is moved, invalidate the route for all MultiNodePathLinks that go through it
if (e.change === go.ChangedEvent.Property && e.propertyName === "location" && e.object instanceof go.Node) {
var diagram = e.diagram;
var node = e.object;
if (node._PathLinks) {
node._PathLinks.each(function(l) { l.invalidateRoute(); });
}
} else if (e.change === go.ChangedEvent.Remove && e.object instanceof go.Layer) {
// when a Node is deleted that has MultiNodePathLinks going through it, invalidate those link routes
if (e.oldValue instanceof go.Node) {
var node = e.oldValue;
if (node._PathLinks) {
node._PathLinks.each(function(l) { l.invalidateRoute(); });
}
} else if (e.oldValue instanceof MultiNodePathLink) {
// when deleting a MultiNodePathLink, remove all references to it in Node._PathLinks
var link = e.oldValue;
var diagram = e.diagram;
var midkeys = link.data.path;
if (Array.isArray(midkeys)) {
for (var i = 0; i < midkeys.length; i++) {
var node = diagram.findNodeForKey(midkeys[i]);
if (node !== null && node._PathLinks) node._PathLinks.remove(link);
}
}
}
}
}
// create a few nodes and links
myDiagram.model = new go.GraphLinksModel([
{ key: 1, text: "Alpha", color: "lightyellow", loc: "0 0" },
{ key: 2, text: "Beta", color: "brown", loc: "200 0" },
{ key: 3, text: "Gamma", color: "green", loc: "300 100" },
{ key: 4, text: "Delta", color: "slateblue", loc: "100 200" },
{ key: 5, text: "Epsilon", color: "aquamarine", loc: "300 350" },
{ key: 6, text: "Zeta", color: "tomato", loc: "0 100" },
{ key: 7, text: "Eta", color: "goldenrod", loc: "0 300" },
{ key: 8, text: "Theta", color: "orange", loc: "300 200" },
], [
{ from: 1, to: 5, path: [2, 3, 4], color: "blue" },
{ from: 6, to: 5, path: [7, 4, 8], color: "red" }
]);
}
function MultiNodePathLink() {
go.Link.call(this);
}
go.Diagram.inherit(MultiNodePathLink, go.Link);
// ignores this.routing, this.adjusting, this.corner, this.smoothness, this.curviness
/** @override */
MultiNodePathLink.prototype.computePoints = function() {
// get the list of Nodes that should be along the path
var nodes = [];
if (this.fromNode !== null && this.fromNode.location.isReal()) {
nodes.push(this.fromNode);
}
var midkeys = this.data.path;
if (Array.isArray(midkeys)) {
var diagram = this.diagram;
for (var i = 0; i < midkeys.length; i++) {
var node = diagram.findNodeForKey(midkeys[i]);
if (node instanceof go.Node && node.location.isReal()) {
nodes.push(node);
// Optimization?: remember on each path Node all of
// the MultiNodePathLinks that go through it;
// but this optimization requires maintaining this cache
// in a Diagram Changed event listener.
var set = node._PathLinks;
if (!set) set = node._PathLinks = new go.Set(go.Link);
set.add(this);
}
}
}
if (this.toNode !== null && this.toNode.location.isReal()) {
nodes.push(this.toNode);
}
// now do the routing
this.clearPoints();
var prevloc = null;
var thisloc = null;
var nextloc = null;
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
thisloc = node.location;
nextloc = (i < nodes.length - 1) ? nodes[i + 1].location : null;
var prevpt = null;
var nextpt = null;
if (this.curve === go.Link.Bezier) {
if (prevloc !== null && nextloc !== null) {
var prevang = thisloc.directionPoint(prevloc);
var nextang = thisloc.directionPoint(nextloc);
var avg = (prevang + nextang) / 2;
var clockwise = prevang > nextang;
if (Math.abs(prevang - nextang) > 180) {
avg += 180;
clockwise = !clockwise;
}
if (avg >= 360) avg -= 360;
prevpt = new go.Point(Math.sqrt(thisloc.distanceSquaredPoint(prevloc)) / 4, 0);
prevpt.rotate(avg + (clockwise ? 90 : -90));
prevpt.add(thisloc);
nextpt = new go.Point(Math.sqrt(thisloc.distanceSquaredPoint(nextloc)) / 4, 0);
nextpt.rotate(avg - (clockwise ? 90 : -90));
nextpt.add(thisloc);
} else if (nextloc !== null) {
prevpt = null;
nextpt = thisloc; // fix this point after the loop
} else if (prevloc !== null) {
var lastpt = this.getPoint(this.pointsCount - 1);
prevpt = thisloc; // fix this point after the loop
nextpt = null;
}
}
if (prevpt !== null) this.addPoint(prevpt);
this.addPoint(thisloc);
if (nextpt !== null) this.addPoint(nextpt);
prevloc = thisloc;
}
// fix up the end points when it's Bezier
if (this.curve === go.Link.Bezier) {
// fix up the first point and the first control point
var start = this.getLinkPointFromPoint(this.fromNode, this.fromPort, this.fromPort.getDocumentPoint(go.Spot.Center), this.getPoint(3), true);
var ctrl2 = this.getPoint(2);
this.setPoint(0, start);
this.setPoint(1, new go.Point((start.x * 3 + ctrl2.x) / 4, (start.y * 3 + ctrl2.y) / 4));
// fix up the last point and the last control point
var end = this.getLinkPointFromPoint(this.toNode, this.toPort, this.toPort.getDocumentPoint(go.Spot.Center), this.getPoint(this.pointsCount - 4), false);
var ctrl1 = this.getPoint(this.pointsCount - 3);
this.setPoint(this.pointsCount - 2, new go.Point((end.x * 3 + ctrl1.x) / 4, (end.y * 3 + ctrl1.y) / 4));
this.setPoint(this.pointsCount - 1, end);
}
return true;
};
// end MultiNodePathLink class
</script>
</head>
<body onload="init()">
<div id="sample">
<div id="myDiagramDiv" style="border: solid 1px blue; width:100%; height:700px; min-width: 200px"></div>
<p>
This sample demonstrates customization of the <a>Link</a>'s routing to go through multiple Nodes.
The nodes are specified by key in the link data's "path" property, which must be an Array of node keys.
</p>
<p>
As the user drags around Nodes on the "path", the routing is automatically recomputed to maintain a smooth curve.
</p>
</div>
</body>
</html>