@knowvah/dot-engine 1.9.0 → 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/README.md +232 -30
- package/dist/api/builder.d.ts +3 -0
- package/dist/api/builder.d.ts.map +1 -1
- package/dist/api/edge-ops.d.ts +7 -0
- package/dist/api/edge-ops.d.ts.map +1 -1
- package/dist/api/geometry.d.ts +5 -2
- package/dist/api/geometry.d.ts.map +1 -1
- package/dist/api.js +159 -31
- package/dist/api.js.map +3 -3
- package/dist/async/collect.d.ts +49 -0
- package/dist/async/collect.d.ts.map +1 -0
- package/dist/async/fonts.d.ts +20 -0
- package/dist/async/fonts.d.ts.map +1 -0
- package/dist/async/render-async.d.ts +91 -0
- package/dist/async/render-async.d.ts.map +1 -0
- package/dist/async/render-into.d.ts +38 -0
- package/dist/async/render-into.d.ts.map +1 -0
- package/dist/async/sanitize.d.ts +28 -0
- package/dist/async/sanitize.d.ts.map +1 -0
- package/dist/common/htmltable-types.d.ts +3 -3
- package/dist/common/htmltable-types.d.ts.map +1 -1
- package/dist/common/make-label.d.ts.map +1 -1
- package/dist/common/poly-shapes.d.ts.map +1 -1
- package/dist/common/textmeasure-factory.d.ts +2 -0
- package/dist/common/textmeasure-factory.d.ts.map +1 -1
- package/dist/common/utils-inputscale.d.ts +19 -0
- package/dist/common/utils-inputscale.d.ts.map +1 -0
- package/dist/errors.d.ts +61 -5
- package/dist/errors.d.ts.map +1 -1
- package/dist/gvc/context.d.ts +36 -4
- package/dist/gvc/context.d.ts.map +1 -1
- package/dist/gvc/device.d.ts +5 -2
- package/dist/gvc/device.d.ts.map +1 -1
- package/dist/gvc/image-resolver.d.ts +16 -16
- package/dist/gvc/image-resolver.d.ts.map +1 -1
- package/dist/gvc/job.d.ts +1 -9
- package/dist/gvc/job.d.ts.map +1 -1
- package/dist/gvc/usershape.d.ts +2 -12
- package/dist/gvc/usershape.d.ts.map +1 -1
- package/dist/index.d.ts +27 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7560 -5446
- package/dist/index.js.map +4 -4
- package/dist/label/index.d.ts.map +1 -1
- package/dist/label/node.d.ts +0 -6
- package/dist/label/node.d.ts.map +1 -1
- package/dist/label/rectangle.d.ts +1 -7
- package/dist/label/rectangle.d.ts.map +1 -1
- package/dist/layout/circo/circular.d.ts +8 -5
- package/dist/layout/circo/circular.d.ts.map +1 -1
- package/dist/layout/dot/pack-components.d.ts +0 -19
- package/dist/layout/dot/pack-components.d.ts.map +1 -1
- package/dist/layout/dot/position.d.ts +7 -2
- package/dist/layout/dot/position.d.ts.map +1 -1
- package/dist/layout/fdp/derive.d.ts.map +1 -1
- package/dist/layout/fdp/index.d.ts.map +1 -1
- package/dist/layout/fdp/init.d.ts.map +1 -1
- package/dist/layout/fdp/layout.d.ts.map +1 -1
- package/dist/layout/fdp/ports.d.ts +0 -10
- package/dist/layout/fdp/ports.d.ts.map +1 -1
- package/dist/layout/fdp/xlayout.d.ts +0 -16
- package/dist/layout/fdp/xlayout.d.ts.map +1 -1
- package/dist/layout/neato/adjust-info.d.ts +117 -0
- package/dist/layout/neato/adjust-info.d.ts.map +1 -0
- package/dist/layout/neato/cdt-surface.d.ts.map +1 -1
- package/dist/layout/neato/constraint-adjust.d.ts +40 -0
- package/dist/layout/neato/constraint-adjust.d.ts.map +1 -0
- package/dist/layout/neato/edge-len.d.ts +20 -0
- package/dist/layout/neato/edge-len.d.ts.map +1 -0
- package/dist/layout/neato/fdp-adjust.d.ts +32 -4
- package/dist/layout/neato/fdp-adjust.d.ts.map +1 -1
- package/dist/layout/neato/index.d.ts +9 -6
- package/dist/layout/neato/index.d.ts.map +1 -1
- package/dist/layout/neato/init.d.ts +4 -11
- package/dist/layout/neato/init.d.ts.map +1 -1
- package/dist/layout/neato/kk-paths.d.ts +50 -0
- package/dist/layout/neato/kk-paths.d.ts.map +1 -0
- package/dist/layout/neato/kk-solve.d.ts +14 -0
- package/dist/layout/neato/kk-solve.d.ts.map +1 -0
- package/dist/layout/neato/kk.d.ts +44 -0
- package/dist/layout/neato/kk.d.ts.map +1 -0
- package/dist/layout/neato/multispline-router.d.ts.map +1 -1
- package/dist/layout/neato/poly.d.ts +50 -0
- package/dist/layout/neato/poly.d.ts.map +1 -0
- package/dist/layout/neato/sgd-dijkstra.d.ts +20 -0
- package/dist/layout/neato/sgd-dijkstra.d.ts.map +1 -0
- package/dist/layout/neato/sgd.d.ts +14 -9
- package/dist/layout/neato/sgd.d.ts.map +1 -1
- package/dist/layout/neato/start.d.ts +51 -0
- package/dist/layout/neato/start.d.ts.map +1 -0
- package/dist/layout/neato/vpsc-adjust.d.ts +21 -0
- package/dist/layout/neato/vpsc-adjust.d.ts.map +1 -0
- package/dist/layout/sfdp/index.d.ts.map +1 -1
- package/dist/layout/sfdp/init.d.ts +0 -9
- package/dist/layout/sfdp/init.d.ts.map +1 -1
- package/dist/layout/sfdp/spring-driver.d.ts.map +1 -1
- package/dist/layout/twopi/circle.d.ts +0 -7
- package/dist/layout/twopi/circle.d.ts.map +1 -1
- package/dist/ortho/ortho-parallel.d.ts.map +1 -1
- package/dist/ortho/trap-query.d.ts.map +1 -1
- package/dist/parser/index.d.ts +9 -5
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/render/index.d.ts +2 -0
- package/dist/render/index.d.ts.map +1 -1
- package/dist/render/public.d.ts +9 -3
- package/dist/render/public.d.ts.map +1 -1
- package/dist/render/xdot-public.d.ts +7 -2
- package/dist/render/xdot-public.d.ts.map +1 -1
- package/dist/render.js +7355 -5476
- package/dist/render.js.map +4 -4
- package/dist/util/xml.d.ts.map +1 -1
- package/dist/vpsc/Solver.d.ts +1 -0
- package/dist/vpsc/Solver.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/api/builder.ts +73 -5
- package/src/api/edge-ops.ts +19 -0
- package/src/api/geometry.ts +22 -5
- package/src/async/collect.ts +204 -0
- package/src/async/fonts.ts +61 -0
- package/src/async/render-async.ts +205 -0
- package/src/async/render-into.ts +115 -0
- package/src/async/sanitize.ts +150 -0
- package/src/common/htmltable-types.ts +4 -5
- package/src/common/make-label.ts +10 -1
- package/src/common/poly-shapes.ts +4 -1
- package/src/common/textmeasure-factory.ts +11 -0
- package/src/common/utils-inputscale.ts +31 -0
- package/src/errors.ts +188 -5
- package/src/gvc/context.ts +96 -17
- package/src/gvc/device.ts +11 -6
- package/src/gvc/image-resolver.ts +25 -2
- package/src/gvc/job.ts +3 -1
- package/src/gvc/usershape.ts +6 -0
- package/src/index.ts +59 -43
- package/src/label/index.ts +6 -2
- package/src/label/node.ts +2 -1
- package/src/label/rectangle.ts +4 -2
- package/src/layout/circo/circular.ts +9 -6
- package/src/layout/dot/pack-components.ts +6 -2
- package/src/layout/dot/position.ts +21 -3
- package/src/layout/fdp/derive.ts +7 -6
- package/src/layout/fdp/index.ts +45 -5
- package/src/layout/fdp/init.ts +7 -6
- package/src/layout/fdp/layout.ts +2 -1
- package/src/layout/fdp/ports.ts +3 -2
- package/src/layout/fdp/xlayout.ts +3 -1
- package/src/layout/neato/adjust-info.ts +332 -0
- package/src/layout/neato/cdt-surface.ts +50 -30
- package/src/layout/neato/constraint-adjust.ts +466 -0
- package/src/layout/neato/edge-len.ts +36 -0
- package/src/layout/neato/fdp-adjust.ts +117 -12
- package/src/layout/neato/index.ts +36 -54
- package/src/layout/neato/init.ts +21 -36
- package/src/layout/neato/kk-paths.ts +146 -0
- package/src/layout/neato/kk-solve.ts +64 -0
- package/src/layout/neato/kk.ts +322 -0
- package/src/layout/neato/multispline-router.ts +4 -3
- package/src/layout/neato/poly.ts +493 -0
- package/src/layout/neato/sgd-dijkstra.ts +125 -0
- package/src/layout/neato/sgd.ts +63 -40
- package/src/layout/neato/start.ts +222 -0
- package/src/layout/neato/vpsc-adjust.ts +93 -0
- package/src/layout/sfdp/index.ts +4 -5
- package/src/layout/sfdp/init.ts +40 -4
- package/src/layout/sfdp/spring-driver.ts +30 -1
- package/src/layout/twopi/circle.ts +2 -1
- package/src/ortho/ortho-parallel.ts +2 -1
- package/src/ortho/trap-query.ts +2 -1
- package/src/parser/index.ts +11 -11
- package/src/render/index.ts +5 -0
- package/src/render/public.ts +24 -24
- package/src/render/svg.ts +1 -1
- package/src/render/xdot-public.ts +19 -18
- package/src/util/xml.ts +24 -30
- package/src/vpsc/Solver.ts +5 -3
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EPL-2.0
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Orthogonal-ordering overlap removal — cAdjust (overlap=ortho*, portho*).
|
|
5
|
+
*
|
|
6
|
+
* Builds horizontal/vertical constraint graphs over the node boxes and solves
|
|
7
|
+
* them with network simplex (a linear objective, so `rank` is reused). Based
|
|
8
|
+
* on Marriott, Stuckey, Tam and He, "Removing Node Overlapping in Graph Layout
|
|
9
|
+
* Using Constrained Optimization", Constraints 8(2):143-172, 2003 (per the C
|
|
10
|
+
* source comment). All geometry is integer points scaled by SCALE (10).
|
|
11
|
+
*
|
|
12
|
+
* Deviations from C, all structural (results identical):
|
|
13
|
+
* - the visibility graph `vg` of mkConstraintG is a per-item adjacency array
|
|
14
|
+
* (C builds a strict cgraph whose out-edge order — by head sequence — equals
|
|
15
|
+
* the ascending list order used here);
|
|
16
|
+
* - cAdjust's `switch` is a mode -> plan table (same eight cases plus the
|
|
17
|
+
* PORTHO default);
|
|
18
|
+
* - constrainX/constrainY share `buildConstraintGraph` for the ortho choice.
|
|
19
|
+
*
|
|
20
|
+
* @see lib/neatogen/constraint.c:cAdjust
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { Graph } from '../../model/graph.js';
|
|
24
|
+
import { Node } from '../../model/node.js';
|
|
25
|
+
import { Edge } from '../../model/edge.js';
|
|
26
|
+
import type { EdgeList } from '../../model/nodeInfo.js';
|
|
27
|
+
import { DtBag } from '../../cdt/index.js';
|
|
28
|
+
import { cround } from '../../common/arith.js';
|
|
29
|
+
import { rank } from '../dot/ns.js';
|
|
30
|
+
import { elistAppend } from '../dot/fastgr.js';
|
|
31
|
+
import { nodesInSeq } from '../dot/decomp.js';
|
|
32
|
+
import { sepFactor } from './sep-factor.js';
|
|
33
|
+
import type { ExpandT } from './sep-factor.js';
|
|
34
|
+
|
|
35
|
+
/** @see lib/neatogen/adjust.h:adjust_mode */
|
|
36
|
+
export const AM_ORTHO = 5;
|
|
37
|
+
export const AM_ORTHO_YX = 6;
|
|
38
|
+
export const AM_ORTHOXY = 7;
|
|
39
|
+
export const AM_ORTHOYX = 8;
|
|
40
|
+
export const AM_PORTHO = 9;
|
|
41
|
+
export const AM_PORTHO_YX = 10;
|
|
42
|
+
export const AM_PORTHOXY = 11;
|
|
43
|
+
export const AM_PORTHOYX = 12;
|
|
44
|
+
|
|
45
|
+
/** For precision, scale up before the algorithms, then scale down. */
|
|
46
|
+
const SCALE = 10;
|
|
47
|
+
const SCALE2 = SCALE / 2;
|
|
48
|
+
/** C `INT_MAX` (rank's maxiter) and `-INT_MAX` (mkConstraintG's sentinel). */
|
|
49
|
+
const INT_MAX = 2147483647;
|
|
50
|
+
/** @see lib/common/geom.h:POINTS_PER_INCH */
|
|
51
|
+
const POINTS_PER_INCH = 72;
|
|
52
|
+
|
|
53
|
+
interface Pt { x: number; y: number }
|
|
54
|
+
interface Box { llx: number; lly: number; urx: number; ury: number }
|
|
55
|
+
|
|
56
|
+
/** @see lib/neatogen/constraint.c:nitem */
|
|
57
|
+
interface Nitem {
|
|
58
|
+
/** Sort key (the dtdisc key, `offsetof(nitem, val)`). */
|
|
59
|
+
val: number;
|
|
60
|
+
/** Position for sorting (integer scaled points). */
|
|
61
|
+
pos: Pt;
|
|
62
|
+
/** Base node. */
|
|
63
|
+
np: Node;
|
|
64
|
+
/** Corresponding node in the constraint graph. */
|
|
65
|
+
cnode: Node | undefined;
|
|
66
|
+
bb: Box;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
type DistFn = (b1: Box, b2: Box) => number;
|
|
70
|
+
type IntersectFn = (p: Nitem, q: Nitem) => boolean;
|
|
71
|
+
type Items = DtBag<Nitem, number>;
|
|
72
|
+
|
|
73
|
+
/** Constraint graph plus the strict-graph edge index (agedge find/create). */
|
|
74
|
+
interface ConstraintGraph {
|
|
75
|
+
g: Graph;
|
|
76
|
+
edges: Map<Node, Map<Node, Edge>>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** @see lib/neatogen/constraint.c:cmpitem */
|
|
80
|
+
function cmpitem(a: number, b: number): number {
|
|
81
|
+
if (a < b) return -1;
|
|
82
|
+
if (a > b) return 1;
|
|
83
|
+
return 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** C int division truncates toward zero. */
|
|
87
|
+
function idiv2(v: number): number {
|
|
88
|
+
return Math.trunc(v / 2);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** @see lib/neatogen/constraint.c:distY */
|
|
92
|
+
function distY(b1: Box, b2: Box): number {
|
|
93
|
+
return idiv2(b1.ury - b1.lly + (b2.ury - b2.lly));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** @see lib/neatogen/constraint.c:distX */
|
|
97
|
+
function distX(b1: Box, b2: Box): number {
|
|
98
|
+
return idiv2(b1.urx - b1.llx + (b2.urx - b2.llx));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* True if boxes could overlap if shifted in y but don't, or if they overlap and
|
|
103
|
+
* a y move is smallest. Assumes q pos above p pos.
|
|
104
|
+
* @see lib/neatogen/constraint.c:intersectX0
|
|
105
|
+
*/
|
|
106
|
+
function intersectX0(p: Nitem, q: Nitem): boolean {
|
|
107
|
+
if (!(p.bb.llx <= q.bb.urx && q.bb.llx <= p.bb.urx)) return false;
|
|
108
|
+
if (p.bb.ury < q.bb.lly) return true;
|
|
109
|
+
const ydelta = distY(p.bb, q.bb) - (q.pos.y - p.pos.y);
|
|
110
|
+
const dx = q.pos.x >= p.pos.x ? q.pos.x - p.pos.x : p.pos.x - q.pos.x;
|
|
111
|
+
const xdelta = distX(p.bb, q.bb) - dx;
|
|
112
|
+
return ydelta <= xdelta;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* True if boxes could overlap if shifted in x but don't, or if they overlap and
|
|
117
|
+
* an x move is smallest. Assumes q pos right of p pos.
|
|
118
|
+
* @see lib/neatogen/constraint.c:intersectY0
|
|
119
|
+
*/
|
|
120
|
+
function intersectY0(p: Nitem, q: Nitem): boolean {
|
|
121
|
+
if (!(p.bb.lly <= q.bb.ury && q.bb.lly <= p.bb.ury)) return false;
|
|
122
|
+
if (p.bb.urx < q.bb.llx) return true;
|
|
123
|
+
const xdelta = distX(p.bb, q.bb) - (q.pos.x - p.pos.x);
|
|
124
|
+
const dy = q.pos.y >= p.pos.y ? q.pos.y - p.pos.y : p.pos.y - q.pos.y;
|
|
125
|
+
const ydelta = distY(p.bb, q.bb) - dy;
|
|
126
|
+
return xdelta <= ydelta;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** @see lib/neatogen/constraint.c:intersectY */
|
|
130
|
+
function intersectY(p: Nitem, q: Nitem): boolean {
|
|
131
|
+
return p.bb.lly <= q.bb.ury && q.bb.lly <= p.bb.ury;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** @see lib/neatogen/constraint.c:intersectX */
|
|
135
|
+
function intersectX(p: Nitem, q: Nitem): boolean {
|
|
136
|
+
return p.bb.llx <= q.bb.urx && q.bb.llx <= p.bb.urx;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** dtflatten order: ascending key, bag-internal order among equal keys. */
|
|
140
|
+
function flatten(list: Items): Nitem[] {
|
|
141
|
+
const out: Nitem[] = [];
|
|
142
|
+
for (let p = list.first(); p !== undefined; p = list.next(p)) out.push(p);
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function newCg(): ConstraintGraph {
|
|
147
|
+
return { g: new Graph('cg', 'strict-directed'), edges: new Map() };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** agnode + agbindrec + alloc_elist for a constraint-graph node. */
|
|
151
|
+
function newCgNode(cg: ConstraintGraph, name: string): Node {
|
|
152
|
+
const n = new Node(cg.g.nodes.size, name, cg.g);
|
|
153
|
+
cg.g.nodes.set(name, n);
|
|
154
|
+
n.info.in = { list: [], size: 0 };
|
|
155
|
+
n.info.out = { list: [], size: 0 };
|
|
156
|
+
return n;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Append n to GD_nlist(cg). */
|
|
160
|
+
function linkNode(cg: ConstraintGraph, last: Node | undefined, n: Node): Node {
|
|
161
|
+
if (last === undefined) cg.g.info.nlist = n;
|
|
162
|
+
else last.info.next = n;
|
|
163
|
+
return n;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** agedge(cg, t, h, NULL, 1): strict, returns the existing edge if any. */
|
|
167
|
+
function cgEdge(cg: ConstraintGraph, t: Node, h: Node): Edge {
|
|
168
|
+
let heads = cg.edges.get(t);
|
|
169
|
+
if (heads === undefined) {
|
|
170
|
+
heads = new Map();
|
|
171
|
+
cg.edges.set(t, heads);
|
|
172
|
+
}
|
|
173
|
+
let e = heads.get(h);
|
|
174
|
+
if (e === undefined) {
|
|
175
|
+
e = new Edge(t, h, '');
|
|
176
|
+
e.info.minlen = 0;
|
|
177
|
+
heads.set(h, e);
|
|
178
|
+
}
|
|
179
|
+
return e;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function listOf(l: EdgeList | undefined): EdgeList {
|
|
183
|
+
if (l === undefined) throw new Error('constraint graph node has no edge list');
|
|
184
|
+
return l;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** elist_append(e, ND_out(t)); elist_append(e, ND_in(h)). */
|
|
188
|
+
function linkEdge(e: Edge): void {
|
|
189
|
+
elistAppend(listOf(e.tail.info.out), e);
|
|
190
|
+
elistAppend(listOf(e.head.info.in), e);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function cnodeOf(p: Nitem): Node {
|
|
194
|
+
if (p.cnode === undefined) throw new Error('nitem has no constraint node');
|
|
195
|
+
return p.cnode;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* agfindedge(g, a, b): an a->b edge, or b->a when g is undirected.
|
|
200
|
+
* @see lib/cgraph/edge.c:agedge
|
|
201
|
+
*/
|
|
202
|
+
function makeHasEdge(g: Graph): (a: Node, b: Node) => boolean {
|
|
203
|
+
const undirected = g.root.kind === 'undirected' || g.root.kind === 'strict-undirected';
|
|
204
|
+
const pairs = new Map<Node, Set<Node>>();
|
|
205
|
+
for (const e of g.edges) {
|
|
206
|
+
const s = pairs.get(e.tail) ?? new Set<Node>();
|
|
207
|
+
s.add(e.head);
|
|
208
|
+
pairs.set(e.tail, s);
|
|
209
|
+
}
|
|
210
|
+
return (a, b) => pairs.get(a)?.has(b) === true || (undirected && pairs.get(b)?.has(a) === true);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* For every item pair, add a constraint when `intersect` holds; no orthogonal
|
|
215
|
+
* ordering is enforced. Edges that exist in `g` get weight 100.
|
|
216
|
+
* @see lib/neatogen/constraint.c:mkNConstraintG
|
|
217
|
+
*/
|
|
218
|
+
function mkNConstraintG(g: Graph, list: Items, intersect: IntersectFn, dist: DistFn): ConstraintGraph {
|
|
219
|
+
const cg = newCg();
|
|
220
|
+
const flat = flatten(list);
|
|
221
|
+
let lastn: Node | undefined;
|
|
222
|
+
for (const p of flat) {
|
|
223
|
+
const n = newCgNode(cg, p.np.name);
|
|
224
|
+
p.cnode = n;
|
|
225
|
+
lastn = linkNode(cg, lastn, n);
|
|
226
|
+
}
|
|
227
|
+
const hasEdge = makeHasEdge(g);
|
|
228
|
+
const created: Edge[] = [];
|
|
229
|
+
for (let i = 0; i < flat.length; i++) {
|
|
230
|
+
const p = flat[i]!;
|
|
231
|
+
for (let j = i + 1; j < flat.length; j++) {
|
|
232
|
+
const nxp = flat[j]!;
|
|
233
|
+
if (!intersect(p, nxp)) continue;
|
|
234
|
+
const e = cgEdge(cg, cnodeOf(p), cnodeOf(nxp));
|
|
235
|
+
e.info.minlen = dist(p.bb, nxp.bb);
|
|
236
|
+
e.info.weight = hasEdge(p.np, nxp.np) ? 100 : 1;
|
|
237
|
+
created.push(e);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
// C walks each node's cgraph out-edges (by head sequence); creation order
|
|
241
|
+
// above is already tail-major and head-ascending, which is that order.
|
|
242
|
+
for (const e of created) linkEdge(e);
|
|
243
|
+
return cg;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Chain one node per distinct val, in order, joined by SCALE-length edges.
|
|
248
|
+
* @see lib/neatogen/constraint.c:mkConstraintG (basic chain)
|
|
249
|
+
*/
|
|
250
|
+
function buildChain(cg: ConstraintGraph, flat: Nitem[]): void {
|
|
251
|
+
let oldval = -INT_MAX;
|
|
252
|
+
let prev: Node | undefined;
|
|
253
|
+
let n: Node | undefined;
|
|
254
|
+
let lastn: Node | undefined;
|
|
255
|
+
for (const p of flat) {
|
|
256
|
+
if (oldval !== p.val) {
|
|
257
|
+
oldval = p.val;
|
|
258
|
+
n = newCgNode(cg, p.np.name);
|
|
259
|
+
lastn = linkNode(cg, lastn, n);
|
|
260
|
+
if (prev !== undefined) {
|
|
261
|
+
const e = cgEdge(cg, prev, n);
|
|
262
|
+
e.info.minlen = SCALE;
|
|
263
|
+
e.info.weight = 1;
|
|
264
|
+
linkEdge(e);
|
|
265
|
+
}
|
|
266
|
+
prev = n;
|
|
267
|
+
}
|
|
268
|
+
p.cnode = n;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Visibility graph as per-item adjacency (head indices ascending).
|
|
274
|
+
* @see lib/neatogen/constraint.c:mkConstraintG (immediate right neighbours)
|
|
275
|
+
*/
|
|
276
|
+
function buildVisibility(flat: Nitem[], intersect: IntersectFn): number[][] {
|
|
277
|
+
const vg: number[][] = flat.map(() => []);
|
|
278
|
+
let oldval = -INT_MAX;
|
|
279
|
+
let nxt = flat.length;
|
|
280
|
+
for (let i = 0; i < flat.length; i++) {
|
|
281
|
+
const p = flat[i]!;
|
|
282
|
+
if (oldval !== p.val) {
|
|
283
|
+
oldval = p.val;
|
|
284
|
+
nxt = i + 1;
|
|
285
|
+
while (nxt < flat.length && flat[nxt]!.val === oldval) nxt++;
|
|
286
|
+
if (nxt >= flat.length) break;
|
|
287
|
+
}
|
|
288
|
+
for (let j = nxt; j < flat.length; j++) {
|
|
289
|
+
if (intersect(p, flat[j]!)) vg[i]!.push(j);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return vg;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Add a constraint edge per visibility edge, with minlen = dist.
|
|
297
|
+
* @see lib/neatogen/constraint.c:mapGraphs
|
|
298
|
+
*/
|
|
299
|
+
function mapGraphs(vg: number[][], flat: Nitem[], cg: ConstraintGraph, dist: DistFn): void {
|
|
300
|
+
for (let i = 0; i < flat.length; i++) {
|
|
301
|
+
const tp = flat[i]!;
|
|
302
|
+
for (const j of vg[i]!) {
|
|
303
|
+
const hp = flat[j]!;
|
|
304
|
+
const delta = dist(tp.bb, hp.bb);
|
|
305
|
+
const ce = cgEdge(cg, cnodeOf(tp), cnodeOf(hp));
|
|
306
|
+
ce.info.weight = 1;
|
|
307
|
+
if ((ce.info.minlen ?? 0) < delta) {
|
|
308
|
+
if (ce.info.minlen === 0) linkEdge(ce);
|
|
309
|
+
ce.info.minlen = delta;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** @see lib/neatogen/constraint.c:mkConstraintG */
|
|
316
|
+
function mkConstraintG(list: Items, intersect: IntersectFn, dist: DistFn): ConstraintGraph {
|
|
317
|
+
const cg = newCg();
|
|
318
|
+
const flat = flatten(list);
|
|
319
|
+
buildChain(cg, flat);
|
|
320
|
+
mapGraphs(buildVisibility(flat, intersect), flat, cg, dist);
|
|
321
|
+
return cg;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function buildConstraintGraph(
|
|
325
|
+
g: Graph, list: Items, ifn: IntersectFn, dist: DistFn, ortho: boolean,
|
|
326
|
+
): ConstraintGraph {
|
|
327
|
+
return ortho ? mkConstraintG(list, ifn, dist) : mkNConstraintG(g, list, ifn, dist);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function newItems(): Items {
|
|
331
|
+
return new DtBag<Nitem, number>((p) => p.val, cmpitem);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Create the X constraints and solve (linear objective, so network simplex).
|
|
336
|
+
* @see lib/neatogen/constraint.c:constrainX
|
|
337
|
+
*/
|
|
338
|
+
function constrainX(g: Graph, nlist: Nitem[], ifn: IntersectFn, ortho: boolean): void {
|
|
339
|
+
const list = newItems();
|
|
340
|
+
for (const p of nlist) {
|
|
341
|
+
p.val = p.pos.x;
|
|
342
|
+
list.insert(p);
|
|
343
|
+
}
|
|
344
|
+
const cg = buildConstraintGraph(g, list, ifn, distX, ortho);
|
|
345
|
+
rank(cg.g, 2, INT_MAX);
|
|
346
|
+
for (const p of nlist) {
|
|
347
|
+
const newpos = cnodeOf(p).info.rank ?? 0;
|
|
348
|
+
const delta = newpos - p.pos.x;
|
|
349
|
+
p.pos.x = newpos;
|
|
350
|
+
p.bb.llx += delta;
|
|
351
|
+
p.bb.urx += delta;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* See constrainX.
|
|
357
|
+
* @see lib/neatogen/constraint.c:constrainY
|
|
358
|
+
*/
|
|
359
|
+
function constrainY(g: Graph, nlist: Nitem[], ifn: IntersectFn, ortho: boolean): void {
|
|
360
|
+
const list = newItems();
|
|
361
|
+
for (const p of nlist) {
|
|
362
|
+
p.val = p.pos.y;
|
|
363
|
+
list.insert(p);
|
|
364
|
+
}
|
|
365
|
+
const cg = buildConstraintGraph(g, list, ifn, distY, ortho);
|
|
366
|
+
rank(cg.g, 2, INT_MAX);
|
|
367
|
+
for (const p of nlist) {
|
|
368
|
+
const newpos = cnodeOf(p).info.rank ?? 0;
|
|
369
|
+
const delta = newpos - p.pos.y;
|
|
370
|
+
p.pos.y = newpos;
|
|
371
|
+
p.bb.lly += delta;
|
|
372
|
+
p.bb.ury += delta;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/** @see lib/neatogen/constraint.c:overlaps (geom.h OVERLAP) */
|
|
377
|
+
function overlaps(p: Nitem[]): boolean {
|
|
378
|
+
for (let i = 0; i < p.length - 1; i++) {
|
|
379
|
+
const a = p[i]!.bb;
|
|
380
|
+
for (let j = i + 1; j < p.length; j++) {
|
|
381
|
+
const b = p[j]!.bb;
|
|
382
|
+
if (a.urx >= b.llx && b.urx >= a.llx && a.ury >= b.lly && b.ury >= a.lly) return true;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return false;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/** @see lib/neatogen/constraint.c:initItem */
|
|
389
|
+
function initItem(n: Node, margin: ExpandT): Nitem {
|
|
390
|
+
const x = cround(SCALE * (n.info.pos?.[0] ?? 0) * POINTS_PER_INCH);
|
|
391
|
+
const y = cround(SCALE * (n.info.pos?.[1] ?? 0) * POINTS_PER_INCH);
|
|
392
|
+
const width = n.info.width ?? 0;
|
|
393
|
+
const height = n.info.height ?? 0;
|
|
394
|
+
let w2: number;
|
|
395
|
+
let h2: number;
|
|
396
|
+
if (margin.doAdd) {
|
|
397
|
+
w2 = Math.trunc(SCALE * (cround((width / 2.0) * POINTS_PER_INCH) + margin.x));
|
|
398
|
+
h2 = Math.trunc(SCALE * (cround((height / 2.0) * POINTS_PER_INCH) + margin.y));
|
|
399
|
+
} else {
|
|
400
|
+
w2 = cround(margin.x * SCALE2 * width * POINTS_PER_INCH);
|
|
401
|
+
h2 = cround(margin.y * SCALE2 * height * POINTS_PER_INCH);
|
|
402
|
+
}
|
|
403
|
+
return {
|
|
404
|
+
val: 0,
|
|
405
|
+
pos: { x, y },
|
|
406
|
+
np: n,
|
|
407
|
+
cnode: undefined,
|
|
408
|
+
bb: { llx: x - w2, lly: y - h2, urx: x + w2, ury: y + h2 },
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/** One cAdjust mode: which axis goes first, the first pass's test, and ortho. */
|
|
413
|
+
interface Plan {
|
|
414
|
+
xFirst: boolean;
|
|
415
|
+
firstTest: IntersectFn;
|
|
416
|
+
ortho: boolean;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/** @see lib/neatogen/constraint.c:cAdjust (switch) */
|
|
420
|
+
const PLANS: ReadonlyMap<number, Plan> = new Map([
|
|
421
|
+
[AM_ORTHOXY, { xFirst: true, firstTest: intersectY, ortho: true }],
|
|
422
|
+
[AM_ORTHOYX, { xFirst: false, firstTest: intersectX, ortho: true }],
|
|
423
|
+
[AM_ORTHO, { xFirst: true, firstTest: intersectY0, ortho: true }],
|
|
424
|
+
[AM_ORTHO_YX, { xFirst: false, firstTest: intersectX0, ortho: true }],
|
|
425
|
+
[AM_PORTHOXY, { xFirst: true, firstTest: intersectY, ortho: false }],
|
|
426
|
+
[AM_PORTHOYX, { xFirst: false, firstTest: intersectX, ortho: false }],
|
|
427
|
+
[AM_PORTHO_YX, { xFirst: false, firstTest: intersectX0, ortho: false }],
|
|
428
|
+
[AM_PORTHO, { xFirst: true, firstTest: intersectY0, ortho: false }],
|
|
429
|
+
]);
|
|
430
|
+
|
|
431
|
+
function runPlan(g: Graph, nlist: Nitem[], plan: Plan): void {
|
|
432
|
+
if (plan.xFirst) {
|
|
433
|
+
constrainX(g, nlist, plan.firstTest, plan.ortho);
|
|
434
|
+
constrainY(g, nlist, intersectX, plan.ortho);
|
|
435
|
+
} else {
|
|
436
|
+
constrainY(g, nlist, plan.firstTest, plan.ortho);
|
|
437
|
+
constrainX(g, nlist, intersectY, plan.ortho);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Remove node overlaps with orthogonal-ordering constraints. mode is one of
|
|
443
|
+
* the eight AM_ORTHO and AM_PORTHO values (else AM_PORTHO). Returns 1
|
|
444
|
+
* if nodes were moved, 0 if no boxes overlapped.
|
|
445
|
+
*
|
|
446
|
+
* ND_pos is in inches; positions are rounded to integer points (x SCALE) while
|
|
447
|
+
* solving, then written back.
|
|
448
|
+
* @see lib/neatogen/constraint.c:cAdjust
|
|
449
|
+
*/
|
|
450
|
+
export function cAdjust(g: Graph, mode: number): number {
|
|
451
|
+
const margin = sepFactor(g);
|
|
452
|
+
const nlist = nodesInSeq(g).map((n) => initItem(n, margin));
|
|
453
|
+
if (!overlaps(nlist)) return 0;
|
|
454
|
+
runPlan(g, nlist, PLANS.get(mode) ?? PLANS.get(AM_PORTHO)!);
|
|
455
|
+
for (const p of nlist) {
|
|
456
|
+
const x = p.pos.x / POINTS_PER_INCH / SCALE;
|
|
457
|
+
const y = p.pos.y / POINTS_PER_INCH / SCALE;
|
|
458
|
+
if (p.np.info.pos) {
|
|
459
|
+
p.np.info.pos[0] = x;
|
|
460
|
+
p.np.info.pos[1] = y;
|
|
461
|
+
} else {
|
|
462
|
+
p.np.info.pos = [x, y];
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return 1;
|
|
466
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EPL-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Edge lengths: scan_graph_mode's MODE_KK and MODE_SGD branches call
|
|
4
|
+
* setEdgeLen on every node, storing `len` (default 1.0) in ED_dist.
|
|
5
|
+
*
|
|
6
|
+
* @see lib/neatogen/stuff.c:setEdgeLen
|
|
7
|
+
* @see lib/neatogen/stuff.c:lenattr
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Graph } from '../../model/graph.js';
|
|
11
|
+
import type { Edge } from '../../model/edge.js';
|
|
12
|
+
|
|
13
|
+
/** `dfltlen` passed by scan_graph_mode. @see lib/neatogen/stuff.c:scan_graph_mode */
|
|
14
|
+
export const DEFAULT_EDGE_LEN = 1.0;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The edge's `len`, or the default when it is absent or empty; a value that
|
|
18
|
+
* does not parse as a positive number warns and falls back to the default.
|
|
19
|
+
* @see lib/neatogen/stuff.c:lenattr
|
|
20
|
+
*/
|
|
21
|
+
export function lenattr(ep: Edge, g: Graph): number {
|
|
22
|
+
const s = ep.attrs.get('len');
|
|
23
|
+
if (s === undefined || s === '') return DEFAULT_EDGE_LEN;
|
|
24
|
+
const val = /^\s*[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?/.exec(s);
|
|
25
|
+
const len = val === null ? Number.NaN : parseFloat(val[0]);
|
|
26
|
+
if (Number.isNaN(len) || len < 0 || len === 0) {
|
|
27
|
+
console.warn(`bad edge len "${s}" in ${g.name} - setting to ${DEFAULT_EDGE_LEN.toFixed(2)}`);
|
|
28
|
+
return DEFAULT_EDGE_LEN;
|
|
29
|
+
}
|
|
30
|
+
return len;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Store every edge's length in `info.dist` (ED_dist). @see lib/neatogen/stuff.c:setEdgeLen */
|
|
34
|
+
export function setEdgeLen(g: Graph): void {
|
|
35
|
+
for (const ep of g.edges) ep.info.dist = lenattr(ep, g);
|
|
36
|
+
}
|
|
@@ -17,9 +17,13 @@ import {
|
|
|
17
17
|
MATRIX_TYPE_REAL,
|
|
18
18
|
} from '../sfdp/sparse-matrix.js';
|
|
19
19
|
import { removeOverlapPrism } from './overlap-prism.js';
|
|
20
|
-
import {
|
|
20
|
+
import { scAdjust } from './sc-adjust.js';
|
|
21
|
+
import { AM_ORTHO, AM_ORTHO_YX, AM_ORTHOXY, AM_ORTHOYX, AM_PORTHO, AM_PORTHO_YX, AM_PORTHOXY, AM_PORTHOYX, cAdjust } from './constraint-adjust.js';
|
|
22
|
+
import { countOverlapIn, sAdjustGraph } from './adjust-info.js';
|
|
23
|
+
import { vpscAdjust } from './vpsc-adjust.js';
|
|
21
24
|
import { sepFactor } from './sep-factor.js';
|
|
22
25
|
import { lateDouble } from '../../common/nodeinit.js';
|
|
26
|
+
import { RenderError } from '../../errors.js';
|
|
23
27
|
|
|
24
28
|
/** @see lib/neatogen/adjust.c:DFLT_MARGIN (points) */
|
|
25
29
|
const DFLT_MARGIN = 4;
|
|
@@ -106,19 +110,120 @@ export function fdpAdjust(g: Graph, ntry: number): void {
|
|
|
106
110
|
}
|
|
107
111
|
}
|
|
108
112
|
|
|
113
|
+
/** Adjust modes, in C's enum order (modes above AM_SCALE take the early switch).
|
|
114
|
+
* @see lib/neatogen/adjust.h:AM_NONE */
|
|
115
|
+
export const AM_NONE = 0;
|
|
116
|
+
export const AM_VOR = 1;
|
|
117
|
+
export const AM_SCALE = 2;
|
|
118
|
+
export const AM_NSCALE = 3;
|
|
119
|
+
export const AM_SCALEXY = 4;
|
|
120
|
+
export const AM_COMPRESS = 13;
|
|
121
|
+
export const AM_VPSC = 14;
|
|
122
|
+
export const AM_IPSEP = 15;
|
|
123
|
+
export const AM_PRISM = 16;
|
|
124
|
+
|
|
125
|
+
/** @see lib/neatogen/adjust.h:adjust_data (mode, print, value) */
|
|
126
|
+
export interface AdjustData {
|
|
127
|
+
mode: number;
|
|
128
|
+
print: string;
|
|
129
|
+
value: number;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** `overlap` value (lowercase) to mode and print string.
|
|
133
|
+
* @see lib/neatogen/adjust.c:adjustMode */
|
|
134
|
+
const ADJUST_MODES = new Map<string, readonly [number, string]>([
|
|
135
|
+
['voronoi', [AM_VOR, 'Voronoi']],
|
|
136
|
+
['scale', [AM_NSCALE, 'scaling']],
|
|
137
|
+
['compress', [AM_COMPRESS, 'compress']],
|
|
138
|
+
['vpsc', [AM_VPSC, 'vpsc']],
|
|
139
|
+
['ipsep', [AM_IPSEP, 'ipsep']],
|
|
140
|
+
['oscale', [AM_SCALE, 'old scaling']],
|
|
141
|
+
['scalexy', [AM_SCALEXY, 'x and y scaling']],
|
|
142
|
+
['ortho', [AM_ORTHO, 'orthogonal constraints']],
|
|
143
|
+
['ortho_yx', [AM_ORTHO_YX, 'orthogonal constraints']],
|
|
144
|
+
['orthoxy', [AM_ORTHOXY, 'xy orthogonal constraints']],
|
|
145
|
+
['orthoyx', [AM_ORTHOYX, 'yx orthogonal constraints']],
|
|
146
|
+
['portho', [AM_PORTHO, 'pseudo-orthogonal constraints']],
|
|
147
|
+
['portho_yx', [AM_PORTHO_YX, 'pseudo-orthogonal constraints']],
|
|
148
|
+
['porthoxy', [AM_PORTHOXY, 'xy pseudo-orthogonal constraints']],
|
|
149
|
+
['porthoyx', [AM_PORTHOYX, 'yx pseudo-orthogonal constraints']],
|
|
150
|
+
]);
|
|
151
|
+
|
|
109
152
|
/**
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* @see lib/neatogen/adjust.c:adjustNodes / removeOverlapWith
|
|
153
|
+
* Resolve an `overlap` value to its adjust mode (the PRISM try count lands in
|
|
154
|
+
* `value`).
|
|
155
|
+
* @see lib/neatogen/adjust.c:getAdjustMode
|
|
114
156
|
*/
|
|
115
|
-
export function
|
|
116
|
-
const flag = g.attrs.get('overlap') ?? g.root.attrs.get('overlap');
|
|
157
|
+
export function getAdjustMode(flag: string | undefined): AdjustData {
|
|
117
158
|
const ntry = overlapPrismTries(flag);
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
159
|
+
if (ntry !== null) return { mode: AM_PRISM, print: 'prism', value: ntry };
|
|
160
|
+
const named = ADJUST_MODES.get((flag ?? '').toLowerCase());
|
|
161
|
+
if (named !== undefined) return { mode: named[0], print: named[1], value: 0 };
|
|
162
|
+
return { mode: AM_NONE, print: 'none', value: 0 };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Modes handled by cAdjust. @see lib/neatogen/adjust.c:removeOverlapWith */
|
|
166
|
+
function isConstraintMode(mode: number): boolean {
|
|
167
|
+
return mode >= AM_ORTHO && mode <= AM_PORTHOYX;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* The `am->mode > AM_SCALE` switch of removeOverlapWith. Under IPSEPCOLA (on in
|
|
172
|
+
* the reference build) ipsep is handled during layout; anything else warns.
|
|
173
|
+
* @see lib/neatogen/adjust.c:removeOverlapWith
|
|
174
|
+
*/
|
|
175
|
+
function adjustLate(g: Graph, am: AdjustData): number {
|
|
176
|
+
if (isConstraintMode(am.mode)) {
|
|
177
|
+
cAdjust(g, am.mode);
|
|
178
|
+
return 0;
|
|
179
|
+
}
|
|
180
|
+
switch (am.mode) {
|
|
181
|
+
case AM_NSCALE: return scAdjust(g, 1);
|
|
182
|
+
case AM_SCALEXY: return scAdjust(g, 0);
|
|
183
|
+
case AM_COMPRESS: return scAdjust(g, -1);
|
|
184
|
+
case AM_PRISM:
|
|
185
|
+
fdpAdjust(g, am.value);
|
|
186
|
+
return 0;
|
|
187
|
+
case AM_IPSEP: return 0;
|
|
188
|
+
case AM_VPSC: return vpscAdjust(g);
|
|
189
|
+
default:
|
|
190
|
+
console.warn(`Unhandled adjust option ${am.print}`);
|
|
191
|
+
return 0;
|
|
122
192
|
}
|
|
123
|
-
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* AM_VOR (`overlap=voronoi`) is vAdjust: it returns 0 when countOverlap finds
|
|
197
|
+
* nothing, otherwise runs the Voronoi adjuster, which is not ported (loud).
|
|
198
|
+
* @see lib/neatogen/adjust.c:vAdjust
|
|
199
|
+
*/
|
|
200
|
+
function adjustVoronoi(g: Graph, am: AdjustData): number {
|
|
201
|
+
if (countOverlapIn(g) === 0) return 0;
|
|
202
|
+
throw new RenderError(
|
|
203
|
+
`overlap=${(g.attrs.get('overlap') ?? g.root.attrs.get('overlap')) ?? am.print}: ` +
|
|
204
|
+
'Voronoi overlap removal is not supported yet',
|
|
205
|
+
'UNSUPPORTED_FEATURE',
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Use `am` to decide if and how to remove node overlaps. normalize and
|
|
211
|
+
* simpleScale (only active with the `normalize`/`scale` attributes) are not
|
|
212
|
+
* ported (see sc-adjust module doc).
|
|
213
|
+
* @see lib/neatogen/adjust.c:removeOverlapWith
|
|
214
|
+
*/
|
|
215
|
+
export function removeOverlapWith(g: Graph, am: AdjustData): number {
|
|
216
|
+
if (g.nodes.size < 2) return 0;
|
|
217
|
+
if (am.mode === AM_NONE) return 0;
|
|
218
|
+
if (am.mode > AM_SCALE) return adjustLate(g, am);
|
|
219
|
+
return am.mode === AM_SCALE ? sAdjustGraph(g) : adjustVoronoi(g, am);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Remove node overlap relying on the graph's `overlap` attribute.
|
|
224
|
+
* @see lib/neatogen/adjust.c:adjustNodes / removeOverlapAs
|
|
225
|
+
*/
|
|
226
|
+
export function adjustNodesFull(g: Graph): number {
|
|
227
|
+
const flag = g.attrs.get('overlap') ?? g.root.attrs.get('overlap');
|
|
228
|
+
return removeOverlapWith(g, getAdjustMode(flag));
|
|
124
229
|
}
|