forked from NorthwoodsSoftware/GoJS
-
Notifications
You must be signed in to change notification settings - Fork 0
/
FishboneLayout.js
336 lines (336 loc) · 15.6 KB
/
FishboneLayout.js
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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
/*
* Copyright (C) 1998-2019 by Northwoods Software Corporation. All Rights Reserved.
*/
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "../release/go"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var go = require("../release/go");
/**
* FishboneLayout is a custom {@link Layout} derived from {@link TreeLayout} for creating "fishbone" diagrams.
* A fishbone diagram also requires a {@link Link} class that implements custom routing, {@link FishboneLink}.
*
* This only works for angle === 0 or angle === 180.
*
* This layout assumes Links are automatically routed in the way needed by fishbone diagrams,
* by using the FishboneLink class instead of go.Link.
*
* If you want to experiment with this extension, try the <a href="../../extensionsTS/Fishbone.html">Fishbone Layout</a> sample.
* @category Layout Extension
*/
var FishboneLayout = /** @class */ (function (_super) {
__extends(FishboneLayout, _super);
/**
* Constructs a FishboneLayout and sets the following properties:
* - {@link #alignment} = {@link TreeLayout.AlignmentBusBranching}
* - {@link #setsPortSpot} = false
* - {@link #setsChildPortSpot} = false
*/
function FishboneLayout() {
var _this = _super.call(this) || this;
_this.alignment = go.TreeLayout.AlignmentBusBranching;
_this.setsPortSpot = false;
_this.setsChildPortSpot = false;
return _this;
}
/**
* Create and initialize a {@link LayoutNetwork} with the given nodes and links.
* This override creates dummy vertexes, when necessary, to allow for proper positioning within the fishbone.
* @param {Diagram|Group|Iterable.<Part>} coll A {@link Diagram} or a {@link Group} or a collection of {@link Part}s.
* @return {LayoutNetwork}
*/
FishboneLayout.prototype.makeNetwork = function (coll) {
// assert(this.angle === 0 || this.angle === 180);
// assert(this.alignment === go.TreeLayout.AlignmentBusBranching);
// assert(this.path !== go.TreeLayout.PathSource);
// call base method for standard behavior
var net = _super.prototype.makeNetwork.call(this, coll);
// make a copy of the collection of TreeVertexes
// because we will be modifying the TreeNetwork.vertexes collection in the loop
var verts = new go.List().addAll(net.vertexes);
verts.each(function (v) {
// ignore leaves of tree
if (v.destinationEdges.count === 0)
return;
if (v.destinationEdges.count % 2 === 1) {
// if there's an odd number of real children, add two dummies
var dummy = net.createVertex();
dummy.bounds = new go.Rect();
dummy.focus = new go.Point();
net.addVertex(dummy);
net.linkVertexes(v, dummy, null);
}
// make sure there's an odd number of children, including at least one dummy;
// commitNodes will move the parent node to where this dummy child node is placed
var dummy2 = net.createVertex();
dummy2.bounds = v.bounds;
dummy2.focus = v.focus;
net.addVertex(dummy2);
net.linkVertexes(v, dummy2, null);
});
return net;
};
/**
* Add a direction property to each vertex and modify {@link TreeVertex#layerSpacing}.
*/
FishboneLayout.prototype.assignTreeVertexValues = function (v) {
_super.prototype.assignTreeVertexValues.call(this, v);
v['_direction'] = 0; // add this property to each TreeVertex
if (v.parent !== null) {
// The parent node will be moved to where the last dummy will be;
// reduce the space to account for the future hole.
if (v.angle === 0 || v.angle === 180) {
v.layerSpacing -= v.bounds.width;
}
else {
v.layerSpacing -= v.bounds.height;
}
}
};
/**
* Assigns {@link Link#fromSpot}s and {@link Link#toSpot}s based on branching and angle
* and moves vertexes based on dummy locations.
*/
FishboneLayout.prototype.commitNodes = function () {
if (this.network === null)
return;
// vertex Angle is set by BusBranching "inheritance";
// assign spots assuming overall Angle === 0 or 180
// and links are always connecting horizontal with vertical
this.network.edges.each(function (e) {
var link = e.link;
if (link === null)
return;
link.fromSpot = go.Spot.None;
link.toSpot = go.Spot.None;
var v = e.fromVertex;
var w = e.toVertex;
if (v.angle === 0) {
link.fromSpot = go.Spot.MiddleLeft;
}
else if (v.angle === 180) {
link.fromSpot = go.Spot.MiddleRight;
}
if (w.angle === 0) {
link.toSpot = go.Spot.MiddleLeft;
}
else if (w.angle === 180) {
link.toSpot = go.Spot.MiddleRight;
}
});
// move the parent node to the location of the last dummy
var vit = this.network.vertexes.iterator;
while (vit.next()) {
var v = vit.value;
var len = v.children.length;
if (len === 0)
continue; // ignore leaf nodes
if (v.parent === null)
continue; // don't move root node
var dummy2 = v.children[len - 1];
v.centerX = dummy2.centerX;
v.centerY = dummy2.centerY;
}
var layout = this;
vit = this.network.vertexes.iterator;
while (vit.next()) {
var v = vit.value;
if (v.parent === null) {
layout.shift(v);
}
}
// now actually change the Node.location of all nodes
_super.prototype.commitNodes.call(this);
};
/**
* This override stops links from being committed since the work is done by the {@link FishboneLink} class.
*/
FishboneLayout.prototype.commitLinks = function () { };
/**
* Shifts subtrees within the fishbone based on angle and node spacing.
*/
FishboneLayout.prototype.shift = function (v) {
var p = v.parent;
if (p !== null && (v.angle === 90 || v.angle === 270)) {
var g = p.parent;
if (g !== null) {
var shift = v.nodeSpacing;
if (g['_direction'] > 0) {
if (g.angle === 90) {
if (p.angle === 0) {
v['_direction'] = 1;
if (v.angle === 270)
this.shiftAll(2, -shift, p, v);
}
else if (p.angle === 180) {
v['_direction'] = -1;
if (v.angle === 90)
this.shiftAll(-2, shift, p, v);
}
}
else if (g.angle === 270) {
if (p.angle === 0) {
v['_direction'] = 1;
if (v.angle === 90)
this.shiftAll(2, -shift, p, v);
}
else if (p.angle === 180) {
v['_direction'] = -1;
if (v.angle === 270)
this.shiftAll(-2, shift, p, v);
}
}
}
else if (g['_direction'] < 0) {
if (g.angle === 90) {
if (p.angle === 0) {
v['_direction'] = 1;
if (v.angle === 90)
this.shiftAll(2, -shift, p, v);
}
else if (p.angle === 180) {
v['_direction'] = -1;
if (v.angle === 270)
this.shiftAll(-2, shift, p, v);
}
}
else if (g.angle === 270) {
if (p.angle === 0) {
v['_direction'] = 1;
if (v.angle === 270)
this.shiftAll(2, -shift, p, v);
}
else if (p.angle === 180) {
v['_direction'] = -1;
if (v.angle === 90)
this.shiftAll(-2, shift, p, v);
}
}
}
}
else { // g === null: V is a child of the tree ROOT
var dir = ((p.angle === 0) ? 1 : -1);
v['_direction'] = dir;
this.shiftAll(dir, 0, p, v);
}
}
for (var i = 0; i < v.children.length; i++) {
var c = v.children[i];
this.shift(c);
}
};
/**
* Shifts a subtree.
*/
FishboneLayout.prototype.shiftAll = function (direction, absolute, root, v) {
// assert(root.angle === 0 || root.angle === 180);
var locx = v.centerX;
locx += direction * Math.abs(root.centerY - v.centerY) / 2;
locx += absolute;
v.centerX = locx;
for (var i = 0; i < v.children.length; i++) {
var c = v.children[i];
this.shiftAll(direction, absolute, root, c);
}
};
return FishboneLayout;
}(go.TreeLayout));
exports.FishboneLayout = FishboneLayout;
/**
* Custom {@link Link} class for {@link FishboneLayout}.
* @category Part Extension
*/
var FishboneLink = /** @class */ (function (_super) {
__extends(FishboneLink, _super);
function FishboneLink() {
return _super !== null && _super.apply(this, arguments) || this;
}
/**
* Determines the points for this link based on spots and maintains horizontal lines.
*/
FishboneLink.prototype.computePoints = function () {
var result = _super.prototype.computePoints.call(this);
if (result) {
// insert middle point to maintain horizontal lines
if (this.fromSpot.equals(go.Spot.MiddleRight) || this.fromSpot.equals(go.Spot.MiddleLeft)) {
var p1 = void 0;
// deal with root node being on the "wrong" side
var fromnode = this.fromNode;
var fromport = this.fromPort;
if (fromnode !== null && fromport !== null && fromnode.findLinksInto().count === 0) {
// pretend the link is coming from the opposite direction than the declared FromSpot
var fromctr = fromport.getDocumentPoint(go.Spot.Center);
var fromfar = fromctr.copy();
fromfar.x += (this.fromSpot.equals(go.Spot.MiddleLeft) ? 99999 : -99999);
p1 = this.getLinkPointFromPoint(fromnode, fromport, fromctr, fromfar, true).copy();
// update the route points
this.setPoint(0, p1);
var endseg = this.fromEndSegmentLength;
if (isNaN(endseg))
endseg = fromport.fromEndSegmentLength;
p1.x += (this.fromSpot.equals(go.Spot.MiddleLeft)) ? endseg : -endseg;
this.setPoint(1, p1);
}
else {
p1 = this.getPoint(1); // points 0 & 1 should be OK already
}
var tonode = this.toNode;
var toport = this.toPort;
if (tonode !== null && toport !== null) {
var toctr = toport.getDocumentPoint(go.Spot.Center);
var far = toctr.copy();
far.x += (this.fromSpot.equals(go.Spot.MiddleLeft)) ? -99999 / 2 : 99999 / 2;
far.y += (toctr.y < p1.y) ? 99999 : -99999;
var p2 = this.getLinkPointFromPoint(tonode, toport, toctr, far, false);
this.setPoint(2, p2);
var dx = Math.abs(p2.y - p1.y) / 2;
if (this.fromSpot.equals(go.Spot.MiddleLeft))
dx = -dx;
this.insertPoint(2, new go.Point(p2.x + dx, p1.y));
}
}
else if (this.toSpot.equals(go.Spot.MiddleRight) || this.toSpot.equals(go.Spot.MiddleLeft)) {
var p1 = this.getPoint(1); // points 1 & 2 should be OK already
var fromnode = this.fromNode;
var fromport = this.fromPort;
if (fromnode !== null && fromport !== null) {
var parentlink = fromnode.findLinksInto().first();
var fromctr = fromport.getDocumentPoint(go.Spot.Center);
var far = fromctr.copy();
far.x += (parentlink !== null && parentlink.fromSpot.equals(go.Spot.MiddleLeft)) ? -99999 / 2 : 99999 / 2;
far.y += (fromctr.y < p1.y) ? 99999 : -99999;
var p0 = this.getLinkPointFromPoint(fromnode, fromport, fromctr, far, true);
this.setPoint(0, p0);
var dx = Math.abs(p1.y - p0.y) / 2;
if (parentlink !== null && parentlink.fromSpot.equals(go.Spot.MiddleLeft))
dx = -dx;
this.insertPoint(1, new go.Point(p0.x + dx, p1.y));
}
}
}
return result;
};
return FishboneLink;
}(go.Link));
exports.FishboneLink = FishboneLink;
});