1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 | import * as go from '../release/go-module.js';
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 | export class SwimLaneLayout extends go.LayeredDigraphLayout {
|
29 | constructor() {
|
30 | super(...arguments);
|
31 |
|
32 | this._laneProperty = "lane";
|
33 | this._laneNames = [];
|
34 | this._laneComparer = null;
|
35 | this._laneSpacing = 0;
|
36 | this._router = { linkSpacing: 4 };
|
37 | this._reducer = null;
|
38 |
|
39 | this._lanePositions = new go.Map();
|
40 | this._laneBreadths = new go.Map();
|
41 |
|
42 | this._layers = [[]];
|
43 | this._neededSpaces = [];
|
44 | }
|
45 | |
46 |
|
47 |
|
48 |
|
49 | get laneProperty() { return this._laneProperty; }
|
50 | set laneProperty(val) {
|
51 | if (typeof val !== 'string' && typeof val !== 'function')
|
52 | throw new Error("new value for SwimLaneLayout.laneProperty must be a property name, not: " + val);
|
53 | if (this._laneProperty !== val) {
|
54 | this._laneProperty = val;
|
55 | this.invalidateLayout();
|
56 | }
|
57 | }
|
58 | |
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 | get laneNames() { return this._laneNames; }
|
67 | set laneNames(val) {
|
68 | if (!Array.isArray(val))
|
69 | throw new Error("new value for SwimLaneLayout.laneNames must be an Array, not: " + val);
|
70 | if (this._laneNames !== val) {
|
71 | this._laneNames = val;
|
72 | this.invalidateLayout();
|
73 | }
|
74 | }
|
75 | |
76 |
|
77 |
|
78 |
|
79 | get laneComparer() { return this._laneComparer; }
|
80 | set laneComparer(val) {
|
81 | if (typeof val !== 'function')
|
82 | throw new Error("new value for SwimLaneLayout.laneComparer must be a function, not: " + val);
|
83 | if (this._laneComparer !== val) {
|
84 | this._laneComparer = val;
|
85 | this.invalidateLayout();
|
86 | }
|
87 | }
|
88 | |
89 |
|
90 |
|
91 |
|
92 |
|
93 |
|
94 | get laneSpacing() { return this._laneSpacing; }
|
95 | set laneSpacing(val) {
|
96 | if (typeof val !== 'number')
|
97 | throw new Error("new value for SwimLaneLayout.laneSpacing must be a number, not: " + val);
|
98 | if (this._laneSpacing !== val) {
|
99 | this._laneSpacing = val;
|
100 | this.invalidateLayout();
|
101 | }
|
102 | }
|
103 | |
104 |
|
105 |
|
106 | get router() { return this._router; }
|
107 | set router(val) {
|
108 | if (this._router !== val) {
|
109 | this._router = val;
|
110 | this.invalidateLayout();
|
111 | }
|
112 | }
|
113 | |
114 |
|
115 |
|
116 | get reducer() { return this._reducer; }
|
117 | set reducer(val) {
|
118 | if (this._reducer !== val) {
|
119 | this._reducer = val;
|
120 | if (val) {
|
121 | const lay = this;
|
122 | val.findLane = function (v) { return lay.getLane(v); };
|
123 | val.getIndex = function (v) { return v.index; };
|
124 | val.getBary = function (v) { return v.bary || 0; };
|
125 | val.setBary = function (v, val) { v.bary = val; };
|
126 | val.getConnectedNodesIterator = function (v) { return v.vertexes; };
|
127 | }
|
128 | this.invalidateLayout();
|
129 | }
|
130 | }
|
131 | |
132 |
|
133 |
|
134 |
|
135 | get lanePositions() { return this._lanePositions; }
|
136 | |
137 |
|
138 |
|
139 |
|
140 | get laneBreadths() { return this._laneBreadths; }
|
141 | |
142 |
|
143 |
|
144 |
|
145 | doLayout(coll) {
|
146 | this.lanePositions.clear();
|
147 | this.laneBreadths.clear();
|
148 | this._layers = [[]];
|
149 | this._neededSpaces = [];
|
150 | super.doLayout(coll);
|
151 | this.lanePositions.clear();
|
152 | this.laneBreadths.clear();
|
153 | this._layers = [[]];
|
154 | this._neededSpaces = [];
|
155 | this.laneNames = [];
|
156 | }
|
157 | |
158 |
|
159 |
|
160 |
|
161 |
|
162 | nodeMinLayerSpace(v, topleft) {
|
163 | if (!this._neededSpaces)
|
164 | this._neededSpaces = this.computeNeededLayerSpaces(this.network);
|
165 | if (v.node === null)
|
166 | return 0;
|
167 | let lay = v.layer;
|
168 | if (!topleft) {
|
169 | if (lay > 0)
|
170 | lay--;
|
171 | }
|
172 | const overlaps = (this._neededSpaces[lay] || 0) / 2;
|
173 | const edges = this.countEdgesForDirection(v, (this.direction > 135) ? !topleft : topleft);
|
174 | const needed = Math.max(overlaps, edges) * this.router.linkSpacing * 1.5;
|
175 | if (this.direction === 90 || this.direction === 270) {
|
176 | if (topleft) {
|
177 | return v.focus.y + 10 + needed;
|
178 | }
|
179 | else {
|
180 | return v.bounds.height - v.focus.y + 10 + needed;
|
181 | }
|
182 | }
|
183 | else {
|
184 | if (topleft) {
|
185 | return v.focus.x + 10 + needed;
|
186 | }
|
187 | else {
|
188 | return v.bounds.width - v.focus.x + 10 + needed;
|
189 | }
|
190 | }
|
191 | }
|
192 | countEdgesForDirection(vertex, topleft) {
|
193 | let c = 0;
|
194 | const lay = vertex.layer;
|
195 | vertex.edges.each(function (e) {
|
196 | if (topleft) {
|
197 | if (e.getOtherVertex(vertex).layer >= lay)
|
198 | c++;
|
199 | }
|
200 | else {
|
201 | if (e.getOtherVertex(vertex).layer <= lay)
|
202 | c++;
|
203 | }
|
204 | });
|
205 | return c;
|
206 | }
|
207 | computeNeededLayerSpaces(net) {
|
208 |
|
209 | const layerMinEdges = [];
|
210 | net.edges.each(function (e) {
|
211 |
|
212 | const f = e.fromVertex;
|
213 | const t = e.toVertex;
|
214 | if (f.column === t.column)
|
215 | return;
|
216 | if (Math.abs(f.layer - t.layer) > 1)
|
217 | return;
|
218 | const lay = Math.min(f.layer, t.layer);
|
219 | let arr = layerMinEdges[lay];
|
220 | if (!arr)
|
221 | arr = layerMinEdges[lay] = [];
|
222 | arr.push(e);
|
223 | });
|
224 |
|
225 |
|
226 | const layerMaxEdges = [];
|
227 | layerMinEdges.forEach(function (arr, lay) {
|
228 | if (!arr)
|
229 | return;
|
230 | arr.sort(function (e1, e2) {
|
231 | const f1c = e1.fromVertex.column;
|
232 | const t1c = e1.toVertex.column;
|
233 | const f2c = e2.fromVertex.column;
|
234 | const t2c = e2.toVertex.column;
|
235 | const e1mincol = Math.min(f1c, t1c);
|
236 | const e2mincol = Math.min(f2c, t2c);
|
237 | if (e1mincol > e2mincol)
|
238 | return 1;
|
239 | if (e1mincol < e2mincol)
|
240 | return -1;
|
241 | const e1maxcol = Math.max(f1c, t1c);
|
242 | const e2maxcol = Math.max(f2c, t2c);
|
243 | if (e1maxcol > e2maxcol)
|
244 | return 1;
|
245 | if (e1maxcol < e2maxcol)
|
246 | return -1;
|
247 | return 0;
|
248 | });
|
249 | layerMaxEdges[lay] = arr.slice(0);
|
250 | layerMaxEdges[lay].sort(function (e1, e2) {
|
251 | const f1c = e1.fromVertex.column;
|
252 | const t1c = e1.toVertex.column;
|
253 | const f2c = e2.fromVertex.column;
|
254 | const t2c = e2.toVertex.column;
|
255 | const e1maxcol = Math.max(f1c, t1c);
|
256 | const e2maxcol = Math.max(f2c, t2c);
|
257 | if (e1maxcol > e2maxcol)
|
258 | return 1;
|
259 | if (e1maxcol < e2maxcol)
|
260 | return -1;
|
261 | const e1mincol = Math.min(f1c, t1c);
|
262 | const e2mincol = Math.min(f2c, t2c);
|
263 | if (e1mincol > e2mincol)
|
264 | return 1;
|
265 | if (e1mincol < e2mincol)
|
266 | return -1;
|
267 | return 0;
|
268 | });
|
269 | });
|
270 |
|
271 | const layerOverlaps = [];
|
272 | layerMinEdges.forEach(function (arr, lay) {
|
273 | const mins = arr;
|
274 | const maxs = layerMaxEdges[lay];
|
275 | let maxoverlap = 0;
|
276 | if (mins && maxs && mins.length > 1 && maxs.length > 1) {
|
277 | let mini = 0;
|
278 | let min = null;
|
279 | let maxi = 0;
|
280 | let max = null;
|
281 | while (mini < mins.length || maxi < maxs.length) {
|
282 | if (mini < mins.length)
|
283 | min = mins[mini];
|
284 | const mincol = min ? Math.min(min.fromVertex.column, min.toVertex.column) : 0;
|
285 | if (maxi < maxs.length)
|
286 | max = maxs[maxi];
|
287 | const maxcol = max ? Math.max(max.fromVertex.column, max.toVertex.column) : Infinity;
|
288 | maxoverlap = Math.max(maxoverlap, Math.abs(mini - maxi));
|
289 | if (mincol <= maxcol && mini < mins.length) {
|
290 | mini++;
|
291 | }
|
292 | else if (maxi < maxs.length) {
|
293 | maxi++;
|
294 | }
|
295 | }
|
296 | }
|
297 | layerOverlaps[lay] = maxoverlap * 1.5;
|
298 | });
|
299 | return layerOverlaps;
|
300 | }
|
301 | setupLanes() {
|
302 |
|
303 | const layout = this;
|
304 | const laneNameSet = new go.Set().addAll(this.laneNames);
|
305 | const laneIndexes = new go.Map();
|
306 | const vit = this.network.vertexes.iterator;
|
307 | while (vit.next()) {
|
308 | const v = vit.value;
|
309 | const lane = this.getLane(v);
|
310 | if (lane !== null && !laneNameSet.has(lane)) {
|
311 | laneNameSet.add(lane);
|
312 | this.laneNames.push(lane);
|
313 | }
|
314 | const layer = v.layer;
|
315 | if (layer >= 0) {
|
316 | const arr = this._layers[layer];
|
317 | if (!arr) {
|
318 | this._layers[layer] = [v];
|
319 | }
|
320 | else {
|
321 | arr.push(v);
|
322 | }
|
323 | }
|
324 | }
|
325 |
|
326 | if (typeof this.laneComparer === "function")
|
327 | this.laneNames.sort(this.laneComparer);
|
328 | for (let i = 0; i < this.laneNames.length; i++) {
|
329 | laneIndexes.add(this.laneNames[i], i);
|
330 | }
|
331 |
|
332 |
|
333 | for (let i = 0; i <= this.maxLayer; i++) {
|
334 | this._layers[i].sort(function (a, b) { return layout.compareVertexes(a, b); });
|
335 | }
|
336 | }
|
337 | |
338 |
|
339 |
|
340 |
|
341 | reduceCrossings() {
|
342 | this.setupLanes();
|
343 |
|
344 | const layers = this._layers;
|
345 | const red = this.reducer;
|
346 | if (red) {
|
347 | for (let i = 0; i < layers.length - 1; i++) {
|
348 | red.reduceCrossings(layers[i], layers[i + 1]);
|
349 | layers[i].forEach(function (v, j) { v.index = j; });
|
350 | }
|
351 | for (let i = layers.length - 1; i > 0; i--) {
|
352 | red.reduceCrossings(layers[i], layers[i - 1]);
|
353 | layers[i].forEach(function (v, j) { v.index = j; });
|
354 | }
|
355 | }
|
356 | this.computeLanes();
|
357 | }
|
358 | computeLanes() {
|
359 |
|
360 | for (let i = 0; i < this.laneNames.length; i++) {
|
361 | const lane = this.laneNames[i];
|
362 | this.laneBreadths.add(lane, this.computeMinLaneWidth(lane));
|
363 | }
|
364 | const lwidths = new go.Map();
|
365 | for (let i = 0; i <= this.maxLayer; i++) {
|
366 | const arr = this._layers[i];
|
367 | if (arr) {
|
368 | const layout = this;
|
369 |
|
370 |
|
371 | for (let j = 0; j < arr.length; j++) {
|
372 | const v = arr[j];
|
373 | const w = this.nodeMinColumnSpace(v, true) + 1 + this.nodeMinColumnSpace(v, false);
|
374 | const ln = this.findLane(v) || "";
|
375 | const totw = lwidths.get(ln);
|
376 | if (totw === null) {
|
377 | lwidths.set(ln, w);
|
378 | }
|
379 | else {
|
380 | lwidths.set(ln, totw + w);
|
381 | }
|
382 | }
|
383 | lwidths.each(function (kvp) {
|
384 | const lane = kvp.key;
|
385 | const colsInLayer = kvp.value;
|
386 | const colsMax = layout.laneBreadths.get(lane) || 0;
|
387 | if (colsInLayer > colsMax)
|
388 | layout.laneBreadths.set(lane, colsInLayer);
|
389 | });
|
390 | lwidths.clear();
|
391 | }
|
392 | }
|
393 |
|
394 | let x = 0;
|
395 | for (let i = 0; i < this.laneNames.length; i++) {
|
396 | const lane = this.laneNames[i];
|
397 | this.lanePositions.set(lane, x);
|
398 | const w = this.laneBreadths.get(lane) || 0;
|
399 | x += w + this.laneSpacing;
|
400 | }
|
401 | this.renormalizeColumns();
|
402 | }
|
403 | renormalizeColumns() {
|
404 |
|
405 | for (let i = 0; i < this._layers.length; i++) {
|
406 | let prevlane = null;
|
407 | let c = 0;
|
408 | const arr = this._layers[i];
|
409 | for (let j = 0; j < arr.length; j++) {
|
410 | const v = arr[j];
|
411 | v.index = j;
|
412 | const l = this.findLane(v);
|
413 | if (l && prevlane !== l) {
|
414 | c = this.lanePositions.get(l) || 0;
|
415 | const w = this.laneBreadths.get(l) || 0;
|
416 |
|
417 | let z = this.nodeMinColumnSpace(v, true) + 1 + this.nodeMinColumnSpace(v, false);
|
418 | let k = j + 1;
|
419 | while (k < arr.length && this.findLane(arr[k]) === l) {
|
420 | const vz = arr[k];
|
421 | z += this.nodeMinColumnSpace(vz, true) + 1 + this.nodeMinColumnSpace(vz, false);
|
422 | k++;
|
423 | }
|
424 |
|
425 | if (z < w) {
|
426 | c += Math.floor((w - z) / 2);
|
427 | }
|
428 | }
|
429 | c += this.nodeMinColumnSpace(v, true);
|
430 | v.column = c;
|
431 | c += 1;
|
432 | c += this.nodeMinColumnSpace(v, false);
|
433 | prevlane = l;
|
434 | }
|
435 | }
|
436 | }
|
437 | |
438 |
|
439 |
|
440 |
|
441 | computeMinLaneWidth(lane) { return 0; }
|
442 | |
443 |
|
444 |
|
445 |
|
446 | straightenAndPack() { }
|
447 | |
448 |
|
449 |
|
450 |
|
451 |
|
452 |
|
453 | getLane(v) {
|
454 | if (v === null)
|
455 | return null;
|
456 | const node = v.node;
|
457 | if (node !== null) {
|
458 | const data = node.data;
|
459 | if (data !== null) {
|
460 | let lane = null;
|
461 | if (typeof this.laneProperty === "function") {
|
462 | lane = this.laneProperty(data);
|
463 | }
|
464 | else {
|
465 | lane = data[this.laneProperty];
|
466 | }
|
467 | if (typeof lane === "string")
|
468 | return lane;
|
469 | return "";
|
470 | }
|
471 | }
|
472 | return null;
|
473 | }
|
474 | |
475 |
|
476 |
|
477 |
|
478 |
|
479 |
|
480 |
|
481 | findLane(v) {
|
482 | if (v !== null) {
|
483 | const lane = this.getLane(v);
|
484 | if (lane !== null) {
|
485 | return lane;
|
486 | }
|
487 | else {
|
488 | const srcv = this.findRealSource(v.sourceEdges.first());
|
489 | const dstv = this.findRealDestination(v.destinationEdges.first());
|
490 | const srcLane = this.getLane(srcv);
|
491 | const dstLane = this.getLane(dstv);
|
492 | if (srcLane !== null || dstLane !== null) {
|
493 | if (srcLane === dstLane)
|
494 | return srcLane;
|
495 | if (srcLane !== null)
|
496 | return srcLane;
|
497 | if (dstLane !== null)
|
498 | return dstLane;
|
499 | }
|
500 | }
|
501 | }
|
502 | return null;
|
503 | }
|
504 | findRealSource(e) {
|
505 | if (e === null)
|
506 | return null;
|
507 | const fv = e.fromVertex;
|
508 | if (fv && fv.node)
|
509 | return fv;
|
510 | return this.findRealSource(fv.sourceEdges.first());
|
511 | }
|
512 | findRealDestination(e) {
|
513 | if (e === null)
|
514 | return null;
|
515 | const tv = e.toVertex;
|
516 | if (tv.node)
|
517 | return tv;
|
518 | return this.findRealDestination(tv.destinationEdges.first());
|
519 | }
|
520 | compareVertexes(v, w) {
|
521 | let laneV = this.findLane(v);
|
522 | if (laneV === null)
|
523 | laneV = "";
|
524 | let laneW = this.findLane(w);
|
525 | if (laneW === null)
|
526 | laneW = "";
|
527 | if (laneV < laneW)
|
528 | return -1;
|
529 | if (laneV > laneW)
|
530 | return 1;
|
531 | return 0;
|
532 | }
|
533 | ;
|
534 | }
|