@dagrejs/dagre 1.1.5 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dagre.cjs.js +3 -0
- package/{index.js → dist/dagre.cjs.js.LEGAL.txt} +2 -12
- package/dist/dagre.cjs.js.map +7 -0
- package/dist/dagre.esm.js +3 -0
- package/dist/dagre.esm.js.LEGAL.txt +23 -0
- package/dist/dagre.esm.js.map +7 -0
- package/dist/dagre.js +3384 -4267
- package/dist/dagre.js.LEGAL.txt +23 -0
- package/dist/dagre.js.map +7 -0
- package/dist/dagre.min.js +3 -801
- package/dist/dagre.min.js.LEGAL.txt +23 -0
- package/dist/dagre.min.js.map +7 -0
- package/package.json +18 -23
- package/index.d.ts +0 -147
- package/lib/acyclic.js +0 -67
- package/lib/add-border-segments.js +0 -37
- package/lib/coordinate-system.js +0 -70
- package/lib/data/list.js +0 -58
- package/lib/debug.js +0 -31
- package/lib/greedy-fas.js +0 -124
- package/lib/layout.js +0 -405
- package/lib/nesting-graph.js +0 -126
- package/lib/normalize.js +0 -89
- package/lib/order/add-subgraph-constraints.js +0 -51
- package/lib/order/barycenter.js +0 -26
- package/lib/order/build-layer-graph.js +0 -73
- package/lib/order/cross-count.js +0 -66
- package/lib/order/index.js +0 -81
- package/lib/order/init-order.js +0 -37
- package/lib/order/resolve-conflicts.js +0 -118
- package/lib/order/sort-subgraph.js +0 -73
- package/lib/order/sort.js +0 -56
- package/lib/parent-dummy-chains.js +0 -84
- package/lib/position/bk.js +0 -424
- package/lib/position/index.js +0 -32
- package/lib/rank/feasible-tree.js +0 -95
- package/lib/rank/index.js +0 -54
- package/lib/rank/network-simplex.js +0 -235
- package/lib/rank/util.js +0 -67
- package/lib/util.js +0 -331
- package/lib/version.js +0 -1
package/lib/position/bk.js
DELETED
|
@@ -1,424 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
let Graph = require("@dagrejs/graphlib").Graph;
|
|
4
|
-
let util = require("../util");
|
|
5
|
-
|
|
6
|
-
/*
|
|
7
|
-
* This module provides coordinate assignment based on Brandes and Köpf, "Fast
|
|
8
|
-
* and Simple Horizontal Coordinate Assignment."
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
module.exports = {
|
|
12
|
-
positionX: positionX,
|
|
13
|
-
findType1Conflicts: findType1Conflicts,
|
|
14
|
-
findType2Conflicts: findType2Conflicts,
|
|
15
|
-
addConflict: addConflict,
|
|
16
|
-
hasConflict: hasConflict,
|
|
17
|
-
verticalAlignment: verticalAlignment,
|
|
18
|
-
horizontalCompaction: horizontalCompaction,
|
|
19
|
-
alignCoordinates: alignCoordinates,
|
|
20
|
-
findSmallestWidthAlignment: findSmallestWidthAlignment,
|
|
21
|
-
balance: balance
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
/*
|
|
25
|
-
* Marks all edges in the graph with a type-1 conflict with the "type1Conflict"
|
|
26
|
-
* property. A type-1 conflict is one where a non-inner segment crosses an
|
|
27
|
-
* inner segment. An inner segment is an edge with both incident nodes marked
|
|
28
|
-
* with the "dummy" property.
|
|
29
|
-
*
|
|
30
|
-
* This algorithm scans layer by layer, starting with the second, for type-1
|
|
31
|
-
* conflicts between the current layer and the previous layer. For each layer
|
|
32
|
-
* it scans the nodes from left to right until it reaches one that is incident
|
|
33
|
-
* on an inner segment. It then scans predecessors to determine if they have
|
|
34
|
-
* edges that cross that inner segment. At the end a final scan is done for all
|
|
35
|
-
* nodes on the current rank to see if they cross the last visited inner
|
|
36
|
-
* segment.
|
|
37
|
-
*
|
|
38
|
-
* This algorithm (safely) assumes that a dummy node will only be incident on a
|
|
39
|
-
* single node in the layers being scanned.
|
|
40
|
-
*/
|
|
41
|
-
function findType1Conflicts(g, layering) {
|
|
42
|
-
let conflicts = {};
|
|
43
|
-
|
|
44
|
-
function visitLayer(prevLayer, layer) {
|
|
45
|
-
let
|
|
46
|
-
// last visited node in the previous layer that is incident on an inner
|
|
47
|
-
// segment.
|
|
48
|
-
k0 = 0,
|
|
49
|
-
// Tracks the last node in this layer scanned for crossings with a type-1
|
|
50
|
-
// segment.
|
|
51
|
-
scanPos = 0,
|
|
52
|
-
prevLayerLength = prevLayer.length,
|
|
53
|
-
lastNode = layer[layer.length - 1];
|
|
54
|
-
|
|
55
|
-
layer.forEach((v, i) => {
|
|
56
|
-
let w = findOtherInnerSegmentNode(g, v),
|
|
57
|
-
k1 = w ? g.node(w).order : prevLayerLength;
|
|
58
|
-
|
|
59
|
-
if (w || v === lastNode) {
|
|
60
|
-
layer.slice(scanPos, i+1).forEach(scanNode => {
|
|
61
|
-
g.predecessors(scanNode).forEach(u => {
|
|
62
|
-
let uLabel = g.node(u),
|
|
63
|
-
uPos = uLabel.order;
|
|
64
|
-
if ((uPos < k0 || k1 < uPos) &&
|
|
65
|
-
!(uLabel.dummy && g.node(scanNode).dummy)) {
|
|
66
|
-
addConflict(conflicts, u, scanNode);
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
scanPos = i + 1;
|
|
71
|
-
k0 = k1;
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
return layer;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
layering.length && layering.reduce(visitLayer);
|
|
79
|
-
|
|
80
|
-
return conflicts;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function findType2Conflicts(g, layering) {
|
|
84
|
-
let conflicts = {};
|
|
85
|
-
|
|
86
|
-
function scan(south, southPos, southEnd, prevNorthBorder, nextNorthBorder) {
|
|
87
|
-
let v;
|
|
88
|
-
util.range(southPos, southEnd).forEach(i => {
|
|
89
|
-
v = south[i];
|
|
90
|
-
if (g.node(v).dummy) {
|
|
91
|
-
g.predecessors(v).forEach(u => {
|
|
92
|
-
let uNode = g.node(u);
|
|
93
|
-
if (uNode.dummy &&
|
|
94
|
-
(uNode.order < prevNorthBorder || uNode.order > nextNorthBorder)) {
|
|
95
|
-
addConflict(conflicts, u, v);
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
function visitLayer(north, south) {
|
|
104
|
-
let prevNorthPos = -1,
|
|
105
|
-
nextNorthPos,
|
|
106
|
-
southPos = 0;
|
|
107
|
-
|
|
108
|
-
south.forEach((v, southLookahead) => {
|
|
109
|
-
if (g.node(v).dummy === "border") {
|
|
110
|
-
let predecessors = g.predecessors(v);
|
|
111
|
-
if (predecessors.length) {
|
|
112
|
-
nextNorthPos = g.node(predecessors[0]).order;
|
|
113
|
-
scan(south, southPos, southLookahead, prevNorthPos, nextNorthPos);
|
|
114
|
-
southPos = southLookahead;
|
|
115
|
-
prevNorthPos = nextNorthPos;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
scan(south, southPos, south.length, nextNorthPos, north.length);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
return south;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
layering.length && layering.reduce(visitLayer);
|
|
125
|
-
|
|
126
|
-
return conflicts;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function findOtherInnerSegmentNode(g, v) {
|
|
130
|
-
if (g.node(v).dummy) {
|
|
131
|
-
return g.predecessors(v).find(u => g.node(u).dummy);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function addConflict(conflicts, v, w) {
|
|
136
|
-
if (v > w) {
|
|
137
|
-
let tmp = v;
|
|
138
|
-
v = w;
|
|
139
|
-
w = tmp;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
let conflictsV = conflicts[v];
|
|
143
|
-
if (!conflictsV) {
|
|
144
|
-
conflicts[v] = conflictsV = {};
|
|
145
|
-
}
|
|
146
|
-
conflictsV[w] = true;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function hasConflict(conflicts, v, w) {
|
|
150
|
-
if (v > w) {
|
|
151
|
-
let tmp = v;
|
|
152
|
-
v = w;
|
|
153
|
-
w = tmp;
|
|
154
|
-
}
|
|
155
|
-
return !!conflicts[v] && Object.hasOwn(conflicts[v], w);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/*
|
|
159
|
-
* Try to align nodes into vertical "blocks" where possible. This algorithm
|
|
160
|
-
* attempts to align a node with one of its median neighbors. If the edge
|
|
161
|
-
* connecting a neighbor is a type-1 conflict then we ignore that possibility.
|
|
162
|
-
* If a previous node has already formed a block with a node after the node
|
|
163
|
-
* we're trying to form a block with, we also ignore that possibility - our
|
|
164
|
-
* blocks would be split in that scenario.
|
|
165
|
-
*/
|
|
166
|
-
function verticalAlignment(g, layering, conflicts, neighborFn) {
|
|
167
|
-
let root = {},
|
|
168
|
-
align = {},
|
|
169
|
-
pos = {};
|
|
170
|
-
|
|
171
|
-
// We cache the position here based on the layering because the graph and
|
|
172
|
-
// layering may be out of sync. The layering matrix is manipulated to
|
|
173
|
-
// generate different extreme alignments.
|
|
174
|
-
layering.forEach(layer => {
|
|
175
|
-
layer.forEach((v, order) => {
|
|
176
|
-
root[v] = v;
|
|
177
|
-
align[v] = v;
|
|
178
|
-
pos[v] = order;
|
|
179
|
-
});
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
layering.forEach(layer => {
|
|
183
|
-
let prevIdx = -1;
|
|
184
|
-
layer.forEach(v => {
|
|
185
|
-
let ws = neighborFn(v);
|
|
186
|
-
if (ws.length) {
|
|
187
|
-
ws = ws.sort((a, b) => pos[a] - pos[b]);
|
|
188
|
-
let mp = (ws.length - 1) / 2;
|
|
189
|
-
for (let i = Math.floor(mp), il = Math.ceil(mp); i <= il; ++i) {
|
|
190
|
-
let w = ws[i];
|
|
191
|
-
if (align[v] === v &&
|
|
192
|
-
prevIdx < pos[w] &&
|
|
193
|
-
!hasConflict(conflicts, v, w)) {
|
|
194
|
-
align[w] = v;
|
|
195
|
-
align[v] = root[v] = root[w];
|
|
196
|
-
prevIdx = pos[w];
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
return { root: root, align: align };
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
function horizontalCompaction(g, layering, root, align, reverseSep) {
|
|
207
|
-
// This portion of the algorithm differs from BK due to a number of problems.
|
|
208
|
-
// Instead of their algorithm we construct a new block graph and do two
|
|
209
|
-
// sweeps. The first sweep places blocks with the smallest possible
|
|
210
|
-
// coordinates. The second sweep removes unused space by moving blocks to the
|
|
211
|
-
// greatest coordinates without violating separation.
|
|
212
|
-
let xs = {},
|
|
213
|
-
blockG = buildBlockGraph(g, layering, root, reverseSep),
|
|
214
|
-
borderType = reverseSep ? "borderLeft" : "borderRight";
|
|
215
|
-
|
|
216
|
-
function iterate(setXsFunc, nextNodesFunc) {
|
|
217
|
-
let stack = blockG.nodes();
|
|
218
|
-
let elem = stack.pop();
|
|
219
|
-
let visited = {};
|
|
220
|
-
while (elem) {
|
|
221
|
-
if (visited[elem]) {
|
|
222
|
-
setXsFunc(elem);
|
|
223
|
-
} else {
|
|
224
|
-
visited[elem] = true;
|
|
225
|
-
stack.push(elem);
|
|
226
|
-
stack = stack.concat(nextNodesFunc(elem));
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
elem = stack.pop();
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// First pass, assign smallest coordinates
|
|
234
|
-
function pass1(elem) {
|
|
235
|
-
xs[elem] = blockG.inEdges(elem).reduce((acc, e) => {
|
|
236
|
-
return Math.max(acc, xs[e.v] + blockG.edge(e));
|
|
237
|
-
}, 0);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Second pass, assign greatest coordinates
|
|
241
|
-
function pass2(elem) {
|
|
242
|
-
let min = blockG.outEdges(elem).reduce((acc, e) => {
|
|
243
|
-
return Math.min(acc, xs[e.w] - blockG.edge(e));
|
|
244
|
-
}, Number.POSITIVE_INFINITY);
|
|
245
|
-
|
|
246
|
-
let node = g.node(elem);
|
|
247
|
-
if (min !== Number.POSITIVE_INFINITY && node.borderType !== borderType) {
|
|
248
|
-
xs[elem] = Math.max(xs[elem], min);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
iterate(pass1, blockG.predecessors.bind(blockG));
|
|
253
|
-
iterate(pass2, blockG.successors.bind(blockG));
|
|
254
|
-
|
|
255
|
-
// Assign x coordinates to all nodes
|
|
256
|
-
Object.keys(align).forEach(v => xs[v] = xs[root[v]]);
|
|
257
|
-
|
|
258
|
-
return xs;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
function buildBlockGraph(g, layering, root, reverseSep) {
|
|
263
|
-
let blockGraph = new Graph(),
|
|
264
|
-
graphLabel = g.graph(),
|
|
265
|
-
sepFn = sep(graphLabel.nodesep, graphLabel.edgesep, reverseSep);
|
|
266
|
-
|
|
267
|
-
layering.forEach(layer => {
|
|
268
|
-
let u;
|
|
269
|
-
layer.forEach(v => {
|
|
270
|
-
let vRoot = root[v];
|
|
271
|
-
blockGraph.setNode(vRoot);
|
|
272
|
-
if (u) {
|
|
273
|
-
var uRoot = root[u],
|
|
274
|
-
prevMax = blockGraph.edge(uRoot, vRoot);
|
|
275
|
-
blockGraph.setEdge(uRoot, vRoot, Math.max(sepFn(g, v, u), prevMax || 0));
|
|
276
|
-
}
|
|
277
|
-
u = v;
|
|
278
|
-
});
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
return blockGraph;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/*
|
|
285
|
-
* Returns the alignment that has the smallest width of the given alignments.
|
|
286
|
-
*/
|
|
287
|
-
function findSmallestWidthAlignment(g, xss) {
|
|
288
|
-
return Object.values(xss).reduce((currentMinAndXs, xs) => {
|
|
289
|
-
let max = Number.NEGATIVE_INFINITY;
|
|
290
|
-
let min = Number.POSITIVE_INFINITY;
|
|
291
|
-
|
|
292
|
-
Object.entries(xs).forEach(([v, x]) => {
|
|
293
|
-
let halfWidth = width(g, v) / 2;
|
|
294
|
-
|
|
295
|
-
max = Math.max(x + halfWidth, max);
|
|
296
|
-
min = Math.min(x - halfWidth, min);
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
const newMin = max - min;
|
|
300
|
-
if (newMin < currentMinAndXs[0]) {
|
|
301
|
-
currentMinAndXs = [newMin, xs];
|
|
302
|
-
}
|
|
303
|
-
return currentMinAndXs;
|
|
304
|
-
}, [Number.POSITIVE_INFINITY, null])[1];
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/*
|
|
308
|
-
* Align the coordinates of each of the layout alignments such that
|
|
309
|
-
* left-biased alignments have their minimum coordinate at the same point as
|
|
310
|
-
* the minimum coordinate of the smallest width alignment and right-biased
|
|
311
|
-
* alignments have their maximum coordinate at the same point as the maximum
|
|
312
|
-
* coordinate of the smallest width alignment.
|
|
313
|
-
*/
|
|
314
|
-
function alignCoordinates(xss, alignTo) {
|
|
315
|
-
let alignToVals = Object.values(alignTo),
|
|
316
|
-
alignToMin = util.applyWithChunking(Math.min, alignToVals),
|
|
317
|
-
alignToMax = util.applyWithChunking(Math.max, alignToVals);
|
|
318
|
-
|
|
319
|
-
["u", "d"].forEach(vert => {
|
|
320
|
-
["l", "r"].forEach(horiz => {
|
|
321
|
-
let alignment = vert + horiz,
|
|
322
|
-
xs = xss[alignment];
|
|
323
|
-
|
|
324
|
-
if (xs === alignTo) return;
|
|
325
|
-
|
|
326
|
-
let xsVals = Object.values(xs);
|
|
327
|
-
let delta = alignToMin - util.applyWithChunking(Math.min, xsVals);
|
|
328
|
-
if (horiz !== "l") {
|
|
329
|
-
delta = alignToMax - util.applyWithChunking(Math.max,xsVals);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
if (delta) {
|
|
333
|
-
xss[alignment] = util.mapValues(xs, x => x + delta);
|
|
334
|
-
}
|
|
335
|
-
});
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
function balance(xss, align) {
|
|
340
|
-
return util.mapValues(xss.ul, (num, v) => {
|
|
341
|
-
if (align) {
|
|
342
|
-
return xss[align.toLowerCase()][v];
|
|
343
|
-
} else {
|
|
344
|
-
let xs = Object.values(xss).map(xs => xs[v]).sort((a, b) => a - b);
|
|
345
|
-
return (xs[1] + xs[2]) / 2;
|
|
346
|
-
}
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
function positionX(g) {
|
|
351
|
-
let layering = util.buildLayerMatrix(g);
|
|
352
|
-
let conflicts = Object.assign(
|
|
353
|
-
findType1Conflicts(g, layering),
|
|
354
|
-
findType2Conflicts(g, layering));
|
|
355
|
-
|
|
356
|
-
let xss = {};
|
|
357
|
-
let adjustedLayering;
|
|
358
|
-
["u", "d"].forEach(vert => {
|
|
359
|
-
adjustedLayering = vert === "u" ? layering : Object.values(layering).reverse();
|
|
360
|
-
["l", "r"].forEach(horiz => {
|
|
361
|
-
if (horiz === "r") {
|
|
362
|
-
adjustedLayering = adjustedLayering.map(inner => {
|
|
363
|
-
return Object.values(inner).reverse();
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
let neighborFn = (vert === "u" ? g.predecessors : g.successors).bind(g);
|
|
368
|
-
let align = verticalAlignment(g, adjustedLayering, conflicts, neighborFn);
|
|
369
|
-
let xs = horizontalCompaction(g, adjustedLayering,
|
|
370
|
-
align.root, align.align, horiz === "r");
|
|
371
|
-
if (horiz === "r") {
|
|
372
|
-
xs = util.mapValues(xs, x => -x);
|
|
373
|
-
}
|
|
374
|
-
xss[vert + horiz] = xs;
|
|
375
|
-
});
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
let smallestWidth = findSmallestWidthAlignment(g, xss);
|
|
380
|
-
alignCoordinates(xss, smallestWidth);
|
|
381
|
-
return balance(xss, g.graph().align);
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
function sep(nodeSep, edgeSep, reverseSep) {
|
|
385
|
-
return (g, v, w) => {
|
|
386
|
-
let vLabel = g.node(v);
|
|
387
|
-
let wLabel = g.node(w);
|
|
388
|
-
let sum = 0;
|
|
389
|
-
let delta;
|
|
390
|
-
|
|
391
|
-
sum += vLabel.width / 2;
|
|
392
|
-
if (Object.hasOwn(vLabel, "labelpos")) {
|
|
393
|
-
switch (vLabel.labelpos.toLowerCase()) {
|
|
394
|
-
case "l": delta = -vLabel.width / 2; break;
|
|
395
|
-
case "r": delta = vLabel.width / 2; break;
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
if (delta) {
|
|
399
|
-
sum += reverseSep ? delta : -delta;
|
|
400
|
-
}
|
|
401
|
-
delta = 0;
|
|
402
|
-
|
|
403
|
-
sum += (vLabel.dummy ? edgeSep : nodeSep) / 2;
|
|
404
|
-
sum += (wLabel.dummy ? edgeSep : nodeSep) / 2;
|
|
405
|
-
|
|
406
|
-
sum += wLabel.width / 2;
|
|
407
|
-
if (Object.hasOwn(wLabel, "labelpos")) {
|
|
408
|
-
switch (wLabel.labelpos.toLowerCase()) {
|
|
409
|
-
case "l": delta = wLabel.width / 2; break;
|
|
410
|
-
case "r": delta = -wLabel.width / 2; break;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
if (delta) {
|
|
414
|
-
sum += reverseSep ? delta : -delta;
|
|
415
|
-
}
|
|
416
|
-
delta = 0;
|
|
417
|
-
|
|
418
|
-
return sum;
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
function width(g, v) {
|
|
423
|
-
return g.node(v).width;
|
|
424
|
-
}
|
package/lib/position/index.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
let util = require("../util");
|
|
4
|
-
let positionX = require("./bk").positionX;
|
|
5
|
-
|
|
6
|
-
module.exports = position;
|
|
7
|
-
|
|
8
|
-
function position(g) {
|
|
9
|
-
g = util.asNonCompoundGraph(g);
|
|
10
|
-
|
|
11
|
-
positionY(g);
|
|
12
|
-
Object.entries(positionX(g)).forEach(([v, x]) => g.node(v).x = x);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function positionY(g) {
|
|
16
|
-
let layering = util.buildLayerMatrix(g);
|
|
17
|
-
let rankSep = g.graph().ranksep;
|
|
18
|
-
let prevY = 0;
|
|
19
|
-
layering.forEach(layer => {
|
|
20
|
-
const maxHeight = layer.reduce((acc, v) => {
|
|
21
|
-
const height = g.node(v).height;
|
|
22
|
-
if (acc > height) {
|
|
23
|
-
return acc;
|
|
24
|
-
} else {
|
|
25
|
-
return height;
|
|
26
|
-
}
|
|
27
|
-
}, 0);
|
|
28
|
-
layer.forEach(v => g.node(v).y = prevY + maxHeight / 2);
|
|
29
|
-
prevY += maxHeight + rankSep;
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
var Graph = require("@dagrejs/graphlib").Graph;
|
|
4
|
-
var slack = require("./util").slack;
|
|
5
|
-
|
|
6
|
-
module.exports = feasibleTree;
|
|
7
|
-
|
|
8
|
-
/*
|
|
9
|
-
* Constructs a spanning tree with tight edges and adjusted the input node's
|
|
10
|
-
* ranks to achieve this. A tight edge is one that is has a length that matches
|
|
11
|
-
* its "minlen" attribute.
|
|
12
|
-
*
|
|
13
|
-
* The basic structure for this function is derived from Gansner, et al., "A
|
|
14
|
-
* Technique for Drawing Directed Graphs."
|
|
15
|
-
*
|
|
16
|
-
* Pre-conditions:
|
|
17
|
-
*
|
|
18
|
-
* 1. Graph must be a DAG.
|
|
19
|
-
* 2. Graph must be connected.
|
|
20
|
-
* 3. Graph must have at least one node.
|
|
21
|
-
* 5. Graph nodes must have been previously assigned a "rank" property that
|
|
22
|
-
* respects the "minlen" property of incident edges.
|
|
23
|
-
* 6. Graph edges must have a "minlen" property.
|
|
24
|
-
*
|
|
25
|
-
* Post-conditions:
|
|
26
|
-
*
|
|
27
|
-
* - Graph nodes will have their rank adjusted to ensure that all edges are
|
|
28
|
-
* tight.
|
|
29
|
-
*
|
|
30
|
-
* Returns a tree (undirected graph) that is constructed using only "tight"
|
|
31
|
-
* edges.
|
|
32
|
-
*/
|
|
33
|
-
function feasibleTree(g) {
|
|
34
|
-
var t = new Graph({ directed: false });
|
|
35
|
-
|
|
36
|
-
// Choose arbitrary node from which to start our tree
|
|
37
|
-
var start = g.nodes()[0];
|
|
38
|
-
var size = g.nodeCount();
|
|
39
|
-
t.setNode(start, {});
|
|
40
|
-
|
|
41
|
-
var edge, delta;
|
|
42
|
-
while (tightTree(t, g) < size) {
|
|
43
|
-
edge = findMinSlackEdge(t, g);
|
|
44
|
-
delta = t.hasNode(edge.v) ? slack(g, edge) : -slack(g, edge);
|
|
45
|
-
shiftRanks(t, g, delta);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return t;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/*
|
|
52
|
-
* Finds a maximal tree of tight edges and returns the number of nodes in the
|
|
53
|
-
* tree.
|
|
54
|
-
*/
|
|
55
|
-
function tightTree(t, g) {
|
|
56
|
-
function dfs(v) {
|
|
57
|
-
g.nodeEdges(v).forEach(e => {
|
|
58
|
-
var edgeV = e.v,
|
|
59
|
-
w = (v === edgeV) ? e.w : edgeV;
|
|
60
|
-
if (!t.hasNode(w) && !slack(g, e)) {
|
|
61
|
-
t.setNode(w, {});
|
|
62
|
-
t.setEdge(v, w, {});
|
|
63
|
-
dfs(w);
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
t.nodes().forEach(dfs);
|
|
69
|
-
return t.nodeCount();
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/*
|
|
73
|
-
* Finds the edge with the smallest slack that is incident on tree and returns
|
|
74
|
-
* it.
|
|
75
|
-
*/
|
|
76
|
-
function findMinSlackEdge(t, g) {
|
|
77
|
-
const edges = g.edges();
|
|
78
|
-
|
|
79
|
-
return edges.reduce((acc, edge) => {
|
|
80
|
-
let edgeSlack = Number.POSITIVE_INFINITY;
|
|
81
|
-
if (t.hasNode(edge.v) !== t.hasNode(edge.w)) {
|
|
82
|
-
edgeSlack = slack(g, edge);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (edgeSlack < acc[0]) {
|
|
86
|
-
return [edgeSlack, edge];
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return acc;
|
|
90
|
-
}, [Number.POSITIVE_INFINITY, null])[1];
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function shiftRanks(t, g, delta) {
|
|
94
|
-
t.nodes().forEach(v => g.node(v).rank += delta);
|
|
95
|
-
}
|
package/lib/rank/index.js
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
var rankUtil = require("./util");
|
|
4
|
-
var longestPath = rankUtil.longestPath;
|
|
5
|
-
var feasibleTree = require("./feasible-tree");
|
|
6
|
-
var networkSimplex = require("./network-simplex");
|
|
7
|
-
|
|
8
|
-
module.exports = rank;
|
|
9
|
-
|
|
10
|
-
/*
|
|
11
|
-
* Assigns a rank to each node in the input graph that respects the "minlen"
|
|
12
|
-
* constraint specified on edges between nodes.
|
|
13
|
-
*
|
|
14
|
-
* This basic structure is derived from Gansner, et al., "A Technique for
|
|
15
|
-
* Drawing Directed Graphs."
|
|
16
|
-
*
|
|
17
|
-
* Pre-conditions:
|
|
18
|
-
*
|
|
19
|
-
* 1. Graph must be a connected DAG
|
|
20
|
-
* 2. Graph nodes must be objects
|
|
21
|
-
* 3. Graph edges must have "weight" and "minlen" attributes
|
|
22
|
-
*
|
|
23
|
-
* Post-conditions:
|
|
24
|
-
*
|
|
25
|
-
* 1. Graph nodes will have a "rank" attribute based on the results of the
|
|
26
|
-
* algorithm. Ranks can start at any index (including negative), we'll
|
|
27
|
-
* fix them up later.
|
|
28
|
-
*/
|
|
29
|
-
function rank(g) {
|
|
30
|
-
var ranker = g.graph().ranker;
|
|
31
|
-
if (ranker instanceof Function) {
|
|
32
|
-
return ranker(g);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
switch(g.graph().ranker) {
|
|
36
|
-
case "network-simplex": networkSimplexRanker(g); break;
|
|
37
|
-
case "tight-tree": tightTreeRanker(g); break;
|
|
38
|
-
case "longest-path": longestPathRanker(g); break;
|
|
39
|
-
case "none": break;
|
|
40
|
-
default: networkSimplexRanker(g);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// A fast and simple ranker, but results are far from optimal.
|
|
45
|
-
var longestPathRanker = longestPath;
|
|
46
|
-
|
|
47
|
-
function tightTreeRanker(g) {
|
|
48
|
-
longestPath(g);
|
|
49
|
-
feasibleTree(g);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function networkSimplexRanker(g) {
|
|
53
|
-
networkSimplex(g);
|
|
54
|
-
}
|