@flowgram-vue/free-auto-layout-plugin 0.2.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.
Files changed (58) hide show
  1. package/LICENSE +22 -0
  2. package/dist/index.cjs +2713 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.ts +377 -0
  5. package/dist/index.js +2705 -0
  6. package/dist/index.js.map +1 -0
  7. package/package.json +57 -0
  8. package/src/create-auto-layout-plugin.ts +23 -0
  9. package/src/dagre-layout/acyclic.ts +129 -0
  10. package/src/dagre-layout/graph.ts +71 -0
  11. package/src/dagre-layout/index.ts +9 -0
  12. package/src/dagre-layout/layout.ts +151 -0
  13. package/src/dagre-layout/order.ts +261 -0
  14. package/src/dagre-layout/rank/feasible-tree.ts +102 -0
  15. package/src/dagre-layout/rank/index.ts +9 -0
  16. package/src/dagre-layout/rank/longest-path.ts +79 -0
  17. package/src/dagre-layout/rank/network-simplex.ts +235 -0
  18. package/src/dagre-layout/rank/normalize-ranks.ts +26 -0
  19. package/src/dagre-layout/type.ts +43 -0
  20. package/src/dagre-lib/acyclic.js +72 -0
  21. package/src/dagre-lib/add-border-segments.js +41 -0
  22. package/src/dagre-lib/coordinate-system.js +77 -0
  23. package/src/dagre-lib/data/list.js +63 -0
  24. package/src/dagre-lib/debug.js +34 -0
  25. package/src/dagre-lib/greedy-fas.js +134 -0
  26. package/src/dagre-lib/index.js +75 -0
  27. package/src/dagre-lib/layout.js +449 -0
  28. package/src/dagre-lib/nesting-graph.js +133 -0
  29. package/src/dagre-lib/normalize.js +98 -0
  30. package/src/dagre-lib/order/add-subgraph-constraints.js +57 -0
  31. package/src/dagre-lib/order/barycenter.js +34 -0
  32. package/src/dagre-lib/order/build-layer-graph.js +80 -0
  33. package/src/dagre-lib/order/cross-count.js +78 -0
  34. package/src/dagre-lib/order/index.js +86 -0
  35. package/src/dagre-lib/order/init-order.js +43 -0
  36. package/src/dagre-lib/order/resolve-conflicts.js +128 -0
  37. package/src/dagre-lib/order/sort-subgraph.js +79 -0
  38. package/src/dagre-lib/order/sort.js +62 -0
  39. package/src/dagre-lib/parent-dummy-chains.js +90 -0
  40. package/src/dagre-lib/position/bk.js +431 -0
  41. package/src/dagre-lib/position/index.js +37 -0
  42. package/src/dagre-lib/rank/feasible-tree.js +104 -0
  43. package/src/dagre-lib/rank/index.js +61 -0
  44. package/src/dagre-lib/rank/network-simplex.js +243 -0
  45. package/src/dagre-lib/rank/util.js +70 -0
  46. package/src/dagre-lib/util.js +365 -0
  47. package/src/dagre-lib/version.js +6 -0
  48. package/src/env.d.ts +10 -0
  49. package/src/index.ts +10 -0
  50. package/src/layout/constant.ts +26 -0
  51. package/src/layout/dagre.ts +245 -0
  52. package/src/layout/index.ts +9 -0
  53. package/src/layout/layout.ts +41 -0
  54. package/src/layout/position.ts +72 -0
  55. package/src/layout/store.ts +262 -0
  56. package/src/layout/type.ts +162 -0
  57. package/src/services.ts +174 -0
  58. package/src/type.ts +10 -0
@@ -0,0 +1,449 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ import acyclic from './acyclic';
9
+ import normalize from './normalize';
10
+ import rank from './rank';
11
+ import { normalizeRanks, removeEmptyRanks, util } from './util';
12
+ import parentDummyChains from './parent-dummy-chains';
13
+ import nestingGraph from './nesting-graph';
14
+ import addBorderSegments from './add-border-segments';
15
+ import coordinateSystem from './coordinate-system';
16
+ import order from './order';
17
+ import position from './position';
18
+ import { Graph } from '@dagrejs/graphlib';
19
+
20
+ export {
21
+ layout,
22
+ buildLayoutGraph,
23
+ updateInputGraph,
24
+ makeSpaceForEdgeLabels,
25
+ removeSelfEdges,
26
+ injectEdgeLabelProxies,
27
+ assignRankMinMax,
28
+ removeEdgeLabelProxies,
29
+ insertSelfEdges,
30
+ positionSelfEdges,
31
+ removeBorderNodes,
32
+ fixupEdgeLabelCoords,
33
+ translateGraph,
34
+ assignNodeIntersects,
35
+ reversePointsForReversedEdges,
36
+ };
37
+
38
+ function layout(g, opts) {
39
+ let time = opts && opts.debugTiming ? util.time : util.notime;
40
+ time('layout', () => {
41
+ let layoutGraph = time(' buildLayoutGraph', () => buildLayoutGraph(g));
42
+ time(' runLayout', () => runLayout(layoutGraph, time, opts));
43
+ time(' updateInputGraph', () => updateInputGraph(g, layoutGraph));
44
+ });
45
+ }
46
+
47
+ function runLayout(g, time, opts) {
48
+ time(' makeSpaceForEdgeLabels', () => makeSpaceForEdgeLabels(g));
49
+ time(' removeSelfEdges', () => removeSelfEdges(g));
50
+ time(' acyclic', () => acyclic.run(g));
51
+ time(' nestingGraph.run', () => nestingGraph.run(g));
52
+ time(' rank', () => rank(util.asNonCompoundGraph(g)));
53
+ time(' injectEdgeLabelProxies', () => injectEdgeLabelProxies(g));
54
+ time(' removeEmptyRanks', () => removeEmptyRanks(g));
55
+ time(' nestingGraph.cleanup', () => nestingGraph.cleanup(g));
56
+ time(' normalizeRanks', () => normalizeRanks(g));
57
+ time(' assignRankMinMax', () => assignRankMinMax(g));
58
+ time(' removeEdgeLabelProxies', () => removeEdgeLabelProxies(g));
59
+ time(' normalize.run', () => normalize.run(g));
60
+ time(' parentDummyChains', () => parentDummyChains(g));
61
+ time(' addBorderSegments', () => addBorderSegments(g));
62
+ time(' order', () => order(g, opts));
63
+ time(' insertSelfEdges', () => insertSelfEdges(g));
64
+ time(' adjustCoordinateSystem', () => coordinateSystem.adjust(g));
65
+ time(' position', () => position(g));
66
+ time(' positionSelfEdges', () => positionSelfEdges(g));
67
+ time(' removeBorderNodes', () => removeBorderNodes(g));
68
+ time(' normalize.undo', () => normalize.undo(g));
69
+ time(' fixupEdgeLabelCoords', () => fixupEdgeLabelCoords(g));
70
+ time(' undoCoordinateSystem', () => coordinateSystem.undo(g));
71
+ time(' translateGraph', () => translateGraph(g));
72
+ time(' assignNodeIntersects', () => assignNodeIntersects(g));
73
+ time(' reversePoints', () => reversePointsForReversedEdges(g));
74
+ time(' acyclic.undo', () => acyclic.undo(g));
75
+ }
76
+
77
+ /*
78
+ * Copies final layout information from the layout graph back to the input
79
+ * graph. This process only copies whitelisted attributes from the layout graph
80
+ * to the input graph, so it serves as a good place to determine what
81
+ * attributes can influence layout.
82
+ */
83
+ function updateInputGraph(inputGraph, layoutGraph) {
84
+ inputGraph.nodes().forEach((v) => {
85
+ let inputLabel = inputGraph.node(v);
86
+ let layoutLabel = layoutGraph.node(v);
87
+
88
+ if (inputLabel) {
89
+ inputLabel.x = layoutLabel.x;
90
+ inputLabel.y = layoutLabel.y;
91
+ inputLabel.rank = layoutLabel.rank;
92
+
93
+ if (layoutGraph.children(v).length) {
94
+ inputLabel.width = layoutLabel.width;
95
+ inputLabel.height = layoutLabel.height;
96
+ }
97
+ }
98
+ });
99
+
100
+ inputGraph.edges().forEach((e) => {
101
+ let inputLabel = inputGraph.edge(e);
102
+ let layoutLabel = layoutGraph.edge(e);
103
+
104
+ inputLabel.points = layoutLabel.points;
105
+ if (Object.hasOwn(layoutLabel, 'x')) {
106
+ inputLabel.x = layoutLabel.x;
107
+ inputLabel.y = layoutLabel.y;
108
+ }
109
+ });
110
+
111
+ inputGraph.graph().width = layoutGraph.graph().width;
112
+ inputGraph.graph().height = layoutGraph.graph().height;
113
+ }
114
+
115
+ let graphNumAttrs = ['nodesep', 'edgesep', 'ranksep', 'marginx', 'marginy'];
116
+ let graphDefaults = { ranksep: 50, edgesep: 20, nodesep: 50, rankdir: 'tb' };
117
+ let graphAttrs = ['acyclicer', 'ranker', 'rankdir', 'align'];
118
+ let nodeNumAttrs = ['width', 'height'];
119
+ let nodeDefaults = { width: 0, height: 0 };
120
+ let edgeNumAttrs = ['minlen', 'weight', 'width', 'height', 'labeloffset'];
121
+ let edgeDefaults = {
122
+ minlen: 1,
123
+ weight: 1,
124
+ width: 0,
125
+ height: 0,
126
+ labeloffset: 10,
127
+ labelpos: 'r',
128
+ };
129
+ let edgeAttrs = ['labelpos'];
130
+
131
+ /*
132
+ * Constructs a new graph from the input graph, which can be used for layout.
133
+ * This process copies only whitelisted attributes from the input graph to the
134
+ * layout graph. Thus this function serves as a good place to determine what
135
+ * attributes can influence layout.
136
+ */
137
+ function buildLayoutGraph(inputGraph) {
138
+ let g = new Graph({ multigraph: true, compound: true });
139
+ let graph = canonicalize(inputGraph.graph());
140
+
141
+ g.setGraph(
142
+ Object.assign(
143
+ {},
144
+ graphDefaults,
145
+ selectNumberAttrs(graph, graphNumAttrs),
146
+ util.pick(graph, graphAttrs)
147
+ )
148
+ );
149
+
150
+ inputGraph.nodes().forEach((v) => {
151
+ let node = canonicalize(inputGraph.node(v));
152
+ const newNode = selectNumberAttrs(node, nodeNumAttrs);
153
+ Object.keys(nodeDefaults).forEach((k) => {
154
+ if (newNode[k] === undefined) {
155
+ newNode[k] = nodeDefaults[k];
156
+ }
157
+ });
158
+
159
+ g.setNode(v, newNode);
160
+ g.setParent(v, inputGraph.parent(v));
161
+ });
162
+
163
+ inputGraph.edges().forEach((e) => {
164
+ let edge = canonicalize(inputGraph.edge(e));
165
+ g.setEdge(
166
+ e,
167
+ Object.assign(
168
+ {},
169
+ edgeDefaults,
170
+ selectNumberAttrs(edge, edgeNumAttrs),
171
+ util.pick(edge, edgeAttrs)
172
+ )
173
+ );
174
+ });
175
+
176
+ return g;
177
+ }
178
+
179
+ /*
180
+ * This idea comes from the Gansner paper: to account for edge labels in our
181
+ * layout we split each rank in half by doubling minlen and halving ranksep.
182
+ * Then we can place labels at these mid-points between nodes.
183
+ *
184
+ * We also add some minimal padding to the width to push the label for the edge
185
+ * away from the edge itself a bit.
186
+ */
187
+ function makeSpaceForEdgeLabels(g) {
188
+ let graph = g.graph();
189
+ graph.ranksep /= 2;
190
+ g.edges().forEach((e) => {
191
+ let edge = g.edge(e);
192
+ edge.minlen *= 2;
193
+ if (edge.labelpos.toLowerCase() !== 'c') {
194
+ if (graph.rankdir === 'TB' || graph.rankdir === 'BT') {
195
+ edge.width += edge.labeloffset;
196
+ } else {
197
+ edge.height += edge.labeloffset;
198
+ }
199
+ }
200
+ });
201
+ }
202
+
203
+ /*
204
+ * Creates temporary dummy nodes that capture the rank in which each edge's
205
+ * label is going to, if it has one of non-zero width and height. We do this
206
+ * so that we can safely remove empty ranks while preserving balance for the
207
+ * label's position.
208
+ */
209
+ function injectEdgeLabelProxies(g) {
210
+ g.edges().forEach((e) => {
211
+ let edge = g.edge(e);
212
+ if (edge.width && edge.height) {
213
+ let v = g.node(e.v);
214
+ let w = g.node(e.w);
215
+ let label = { rank: (w.rank - v.rank) / 2 + v.rank, e: e };
216
+ util.addDummyNode(g, 'edge-proxy', label, '_ep');
217
+ }
218
+ });
219
+ }
220
+
221
+ function assignRankMinMax(g) {
222
+ let maxRank = 0;
223
+ g.nodes().forEach((v) => {
224
+ let node = g.node(v);
225
+ if (node.borderTop) {
226
+ node.minRank = g.node(node.borderTop).rank;
227
+ node.maxRank = g.node(node.borderBottom).rank;
228
+ maxRank = Math.max(maxRank, node.maxRank);
229
+ }
230
+ });
231
+ g.graph().maxRank = maxRank;
232
+ }
233
+
234
+ function removeEdgeLabelProxies(g) {
235
+ g.nodes().forEach((v) => {
236
+ let node = g.node(v);
237
+ if (node.dummy === 'edge-proxy') {
238
+ g.edge(node.e).labelRank = node.rank;
239
+ g.removeNode(v);
240
+ }
241
+ });
242
+ }
243
+
244
+ function translateGraph(g) {
245
+ let minX = Number.POSITIVE_INFINITY;
246
+ let maxX = 0;
247
+ let minY = Number.POSITIVE_INFINITY;
248
+ let maxY = 0;
249
+ let graphLabel = g.graph();
250
+ let marginX = graphLabel.marginx || 0;
251
+ let marginY = graphLabel.marginy || 0;
252
+
253
+ function getExtremes(attrs) {
254
+ let x = attrs.x;
255
+ let y = attrs.y;
256
+ let w = attrs.width;
257
+ let h = attrs.height;
258
+ minX = Math.min(minX, x - w / 2);
259
+ maxX = Math.max(maxX, x + w / 2);
260
+ minY = Math.min(minY, y - h / 2);
261
+ maxY = Math.max(maxY, y + h / 2);
262
+ }
263
+
264
+ g.nodes().forEach((v) => getExtremes(g.node(v)));
265
+ g.edges().forEach((e) => {
266
+ let edge = g.edge(e);
267
+ if (Object.hasOwn(edge, 'x')) {
268
+ getExtremes(edge);
269
+ }
270
+ });
271
+
272
+ minX -= marginX;
273
+ minY -= marginY;
274
+
275
+ g.nodes().forEach((v) => {
276
+ let node = g.node(v);
277
+ node.x -= minX;
278
+ node.y -= minY;
279
+ });
280
+
281
+ g.edges().forEach((e) => {
282
+ let edge = g.edge(e);
283
+ edge.points.forEach((p) => {
284
+ p.x -= minX;
285
+ p.y -= minY;
286
+ });
287
+ if (Object.hasOwn(edge, 'x')) {
288
+ edge.x -= minX;
289
+ }
290
+ if (Object.hasOwn(edge, 'y')) {
291
+ edge.y -= minY;
292
+ }
293
+ });
294
+
295
+ graphLabel.width = maxX - minX + marginX;
296
+ graphLabel.height = maxY - minY + marginY;
297
+ }
298
+
299
+ function assignNodeIntersects(g) {
300
+ g.edges().forEach((e) => {
301
+ let edge = g.edge(e);
302
+ let nodeV = g.node(e.v);
303
+ let nodeW = g.node(e.w);
304
+ let p1, p2;
305
+ if (!edge.points) {
306
+ edge.points = [];
307
+ p1 = nodeW;
308
+ p2 = nodeV;
309
+ } else {
310
+ p1 = edge.points[0];
311
+ p2 = edge.points[edge.points.length - 1];
312
+ }
313
+ edge.points.unshift(util.intersectRect(nodeV, p1));
314
+ edge.points.push(util.intersectRect(nodeW, p2));
315
+ });
316
+ }
317
+
318
+ function fixupEdgeLabelCoords(g) {
319
+ g.edges().forEach((e) => {
320
+ let edge = g.edge(e);
321
+ if (Object.hasOwn(edge, 'x')) {
322
+ if (edge.labelpos === 'l' || edge.labelpos === 'r') {
323
+ edge.width -= edge.labeloffset;
324
+ }
325
+ switch (edge.labelpos) {
326
+ case 'l':
327
+ edge.x -= edge.width / 2 + edge.labeloffset;
328
+ break;
329
+ case 'r':
330
+ edge.x += edge.width / 2 + edge.labeloffset;
331
+ break;
332
+ }
333
+ }
334
+ });
335
+ }
336
+
337
+ function reversePointsForReversedEdges(g) {
338
+ g.edges().forEach((e) => {
339
+ let edge = g.edge(e);
340
+ if (edge.reversed) {
341
+ edge.points.reverse();
342
+ }
343
+ });
344
+ }
345
+
346
+ function removeBorderNodes(g) {
347
+ g.nodes().forEach((v) => {
348
+ if (g.children(v).length) {
349
+ let node = g.node(v);
350
+ let t = g.node(node.borderTop);
351
+ let b = g.node(node.borderBottom);
352
+ let l = g.node(node.borderLeft[node.borderLeft.length - 1]);
353
+ let r = g.node(node.borderRight[node.borderRight.length - 1]);
354
+
355
+ node.width = Math.abs(r.x - l.x);
356
+ node.height = Math.abs(b.y - t.y);
357
+ node.x = l.x + node.width / 2;
358
+ node.y = t.y + node.height / 2;
359
+ }
360
+ });
361
+
362
+ g.nodes().forEach((v) => {
363
+ if (g.node(v).dummy === 'border') {
364
+ g.removeNode(v);
365
+ }
366
+ });
367
+ }
368
+
369
+ function removeSelfEdges(g) {
370
+ g.edges().forEach((e) => {
371
+ if (e.v === e.w) {
372
+ var node = g.node(e.v);
373
+ if (!node.selfEdges) {
374
+ node.selfEdges = [];
375
+ }
376
+ node.selfEdges.push({ e: e, label: g.edge(e) });
377
+ g.removeEdge(e);
378
+ }
379
+ });
380
+ }
381
+
382
+ function insertSelfEdges(g) {
383
+ var layers = util.buildLayerMatrix(g);
384
+ layers.forEach((layer) => {
385
+ var orderShift = 0;
386
+ layer.forEach((v, i) => {
387
+ var node = g.node(v);
388
+ node.order = i + orderShift;
389
+ (node.selfEdges || []).forEach((selfEdge) => {
390
+ util.addDummyNode(
391
+ g,
392
+ 'selfedge',
393
+ {
394
+ width: selfEdge.label.width,
395
+ height: selfEdge.label.height,
396
+ rank: node.rank,
397
+ order: i + ++orderShift,
398
+ e: selfEdge.e,
399
+ label: selfEdge.label,
400
+ },
401
+ '_se'
402
+ );
403
+ });
404
+ delete node.selfEdges;
405
+ });
406
+ });
407
+ }
408
+
409
+ function positionSelfEdges(g) {
410
+ g.nodes().forEach((v) => {
411
+ var node = g.node(v);
412
+ if (node.dummy === 'selfedge') {
413
+ var selfNode = g.node(node.e.v);
414
+ var x = selfNode.x + selfNode.width / 2;
415
+ var y = selfNode.y;
416
+ var dx = node.x - x;
417
+ var dy = selfNode.height / 2;
418
+ g.setEdge(node.e, node.label);
419
+ g.removeNode(v);
420
+ node.label.points = [
421
+ { x: x + (2 * dx) / 3, y: y - dy },
422
+ { x: x + (5 * dx) / 6, y: y - dy },
423
+ { x: x + dx, y: y },
424
+ { x: x + (5 * dx) / 6, y: y + dy },
425
+ { x: x + (2 * dx) / 3, y: y + dy },
426
+ ];
427
+ node.label.x = node.x;
428
+ node.label.y = node.y;
429
+ }
430
+ });
431
+ }
432
+
433
+ function selectNumberAttrs(obj, attrs) {
434
+ return util.mapValues(util.pick(obj, attrs), Number);
435
+ }
436
+
437
+ function canonicalize(attrs) {
438
+ var newAttrs = {};
439
+ if (attrs) {
440
+ Object.entries(attrs).forEach(([k, v]) => {
441
+ if (typeof k === 'string') {
442
+ k = k.toLowerCase();
443
+ }
444
+
445
+ newAttrs[k] = v;
446
+ });
447
+ }
448
+ return newAttrs;
449
+ }
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { util } from './util';
7
+
8
+ export const nestingGraph = {
9
+ run,
10
+ cleanup,
11
+ };
12
+
13
+ export default nestingGraph;
14
+
15
+ /*
16
+ * A nesting graph creates dummy nodes for the tops and bottoms of subgraphs,
17
+ * adds appropriate edges to ensure that all cluster nodes are placed between
18
+ * these boundaries, and ensures that the graph is connected.
19
+ *
20
+ * In addition we ensure, through the use of the minlen property, that nodes
21
+ * and subgraph border nodes to not end up on the same rank.
22
+ *
23
+ * Preconditions:
24
+ *
25
+ * 1. Input graph is a DAG
26
+ * 2. Nodes in the input graph has a minlen attribute
27
+ *
28
+ * Postconditions:
29
+ *
30
+ * 1. Input graph is connected.
31
+ * 2. Dummy nodes are added for the tops and bottoms of subgraphs.
32
+ * 3. The minlen attribute for nodes is adjusted to ensure nodes do not
33
+ * get placed on the same rank as subgraph border nodes.
34
+ *
35
+ * The nesting graph idea comes from Sander, "Layout of Compound Directed
36
+ * Graphs."
37
+ */
38
+ function run(g) {
39
+ let root = util.addDummyNode(g, 'root', {}, '_root');
40
+ let depths = treeDepths(g);
41
+ let depthsArr = Object.values(depths);
42
+ let height = util.applyWithChunking(Math.max, depthsArr) - 1; // Note: depths is an Object not an array
43
+ let nodeSep = 2 * height + 1;
44
+
45
+ g.graph().nestingRoot = root;
46
+
47
+ // Multiply minlen by nodeSep to align nodes on non-border ranks.
48
+ g.edges().forEach((e) => (g.edge(e).minlen *= nodeSep));
49
+
50
+ // Calculate a weight that is sufficient to keep subgraphs vertically compact
51
+ let weight = sumWeights(g) + 1;
52
+
53
+ // Create border nodes and link them up
54
+ g.children().forEach((child) => dfs(g, root, nodeSep, weight, height, depths, child));
55
+
56
+ // Save the multiplier for node layers for later removal of empty border
57
+ // layers.
58
+ g.graph().nodeRankFactor = nodeSep;
59
+ }
60
+
61
+ function dfs(g, root, nodeSep, weight, height, depths, v) {
62
+ let children = g.children(v);
63
+ if (!children.length) {
64
+ if (v !== root) {
65
+ g.setEdge(root, v, { weight: 0, minlen: nodeSep });
66
+ }
67
+ return;
68
+ }
69
+
70
+ let top = util.addBorderNode(g, '_bt');
71
+ let bottom = util.addBorderNode(g, '_bb');
72
+ let label = g.node(v);
73
+
74
+ g.setParent(top, v);
75
+ label.borderTop = top;
76
+ g.setParent(bottom, v);
77
+ label.borderBottom = bottom;
78
+
79
+ children.forEach((child) => {
80
+ dfs(g, root, nodeSep, weight, height, depths, child);
81
+
82
+ let childNode = g.node(child);
83
+ let childTop = childNode.borderTop ? childNode.borderTop : child;
84
+ let childBottom = childNode.borderBottom ? childNode.borderBottom : child;
85
+ let thisWeight = childNode.borderTop ? weight : 2 * weight;
86
+ let minlen = childTop !== childBottom ? 1 : height - depths[v] + 1;
87
+
88
+ g.setEdge(top, childTop, {
89
+ weight: thisWeight,
90
+ minlen: minlen,
91
+ nestingEdge: true,
92
+ });
93
+
94
+ g.setEdge(childBottom, bottom, {
95
+ weight: thisWeight,
96
+ minlen: minlen,
97
+ nestingEdge: true,
98
+ });
99
+ });
100
+
101
+ if (!g.parent(v)) {
102
+ g.setEdge(root, top, { weight: 0, minlen: height + depths[v] });
103
+ }
104
+ }
105
+
106
+ function treeDepths(g) {
107
+ var depths = {};
108
+ function dfs(v, depth) {
109
+ var children = g.children(v);
110
+ if (children && children.length) {
111
+ children.forEach((child) => dfs(child, depth + 1));
112
+ }
113
+ depths[v] = depth;
114
+ }
115
+ g.children().forEach((v) => dfs(v, 1));
116
+ return depths;
117
+ }
118
+
119
+ function sumWeights(g) {
120
+ return g.edges().reduce((acc, e) => acc + g.edge(e).weight, 0);
121
+ }
122
+
123
+ function cleanup(g) {
124
+ var graphLabel = g.graph();
125
+ g.removeNode(graphLabel.nestingRoot);
126
+ delete graphLabel.nestingRoot;
127
+ g.edges().forEach((e) => {
128
+ var edge = g.edge(e);
129
+ if (edge.nestingEdge) {
130
+ g.removeEdge(e);
131
+ }
132
+ });
133
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ import { util } from './util';
9
+
10
+ export const normalize = {
11
+ run,
12
+ undo,
13
+ };
14
+
15
+ export default normalize;
16
+
17
+ /*
18
+ * Breaks any long edges in the graph into short segments that span 1 layer
19
+ * each. This operation is undoable with the denormalize function.
20
+ *
21
+ * Pre-conditions:
22
+ *
23
+ * 1. The input graph is a DAG.
24
+ * 2. Each node in the graph has a "rank" property.
25
+ *
26
+ * Post-condition:
27
+ *
28
+ * 1. All edges in the graph have a length of 1.
29
+ * 2. Dummy nodes are added where edges have been split into segments.
30
+ * 3. The graph is augmented with a "dummyChains" attribute which contains
31
+ * the first dummy in each chain of dummy nodes produced.
32
+ */
33
+ function run(g) {
34
+ g.graph().dummyChains = [];
35
+ g.edges().forEach((edge) => normalizeEdge(g, edge));
36
+ }
37
+
38
+ function normalizeEdge(g, e) {
39
+ let v = e.v;
40
+ let vRank = g.node(v).rank;
41
+ let w = e.w;
42
+ let wRank = g.node(w).rank;
43
+ let name = e.name;
44
+ let edgeLabel = g.edge(e);
45
+ let labelRank = edgeLabel.labelRank;
46
+
47
+ if (wRank === vRank + 1) return;
48
+
49
+ g.removeEdge(e);
50
+
51
+ let dummy, attrs, i;
52
+ for (i = 0, ++vRank; vRank < wRank; ++i, ++vRank) {
53
+ edgeLabel.points = [];
54
+ attrs = {
55
+ width: 0,
56
+ height: 0,
57
+ edgeLabel: edgeLabel,
58
+ edgeObj: e,
59
+ rank: vRank,
60
+ };
61
+ dummy = util.addDummyNode(g, 'edge', attrs, '_d');
62
+ if (vRank === labelRank) {
63
+ attrs.width = edgeLabel.width;
64
+ attrs.height = edgeLabel.height;
65
+ attrs.dummy = 'edge-label';
66
+ attrs.labelpos = edgeLabel.labelpos;
67
+ }
68
+ g.setEdge(v, dummy, { weight: edgeLabel.weight }, name);
69
+ if (i === 0) {
70
+ g.graph().dummyChains.push(dummy);
71
+ }
72
+ v = dummy;
73
+ }
74
+
75
+ g.setEdge(v, w, { weight: edgeLabel.weight }, name);
76
+ }
77
+
78
+ function undo(g) {
79
+ g.graph().dummyChains.forEach((v) => {
80
+ let node = g.node(v);
81
+ let origLabel = node.edgeLabel;
82
+ let w;
83
+ g.setEdge(node.edgeObj, origLabel);
84
+ while (node.dummy) {
85
+ w = g.successors(v)[0];
86
+ g.removeNode(v);
87
+ origLabel.points.push({ x: node.x, y: node.y });
88
+ if (node.dummy === 'edge-label') {
89
+ origLabel.x = node.x;
90
+ origLabel.y = node.y;
91
+ origLabel.width = node.width;
92
+ origLabel.height = node.height;
93
+ }
94
+ v = w;
95
+ node = g.node(v);
96
+ }
97
+ });
98
+ }