@knowvah/dot-engine 1.8.1 → 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/css-font.d.ts +9 -0
- package/dist/common/css-font.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/textmeasure.d.ts +11 -1
- package/dist/common/textmeasure.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 +7745 -5550
- 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 +7548 -5588
- 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/css-font.ts +109 -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/textmeasure.ts +41 -8
- 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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Neato layout engine entry point.
|
|
4
4
|
*
|
|
5
|
-
* Wires together neatoInitNode, setSeed, solveModel,
|
|
5
|
+
* Wires together neatoInitNode, setSeed, solveModel, adjustNodes,
|
|
6
6
|
* splineEdges, neatoTranslate, and neatoSetAspect into the full neato
|
|
7
7
|
* layout pipeline, and exports NEATO_LAYOUT_ENGINE for registration with
|
|
8
8
|
* GvcContext.
|
|
@@ -14,28 +14,26 @@
|
|
|
14
14
|
import type { Graph } from '../../model/graph.js';
|
|
15
15
|
import type { LayoutEngine } from '../../gvc/context.js';
|
|
16
16
|
import {
|
|
17
|
+
MODE_KK,
|
|
17
18
|
neatoInitNode,
|
|
18
19
|
userPos,
|
|
19
20
|
solveModel,
|
|
20
21
|
neatoCleanup,
|
|
21
|
-
MODE_KK,
|
|
22
|
-
MODE_MAJOR,
|
|
23
|
-
MODE_HIER,
|
|
24
|
-
MODE_IPSEP,
|
|
25
|
-
MODE_SGD,
|
|
26
22
|
MODEL_SHORTPATH,
|
|
27
23
|
MODEL_CIRCUIT,
|
|
28
24
|
MODEL_SUBSET,
|
|
29
25
|
MODEL_MDS,
|
|
30
26
|
} from './init.js';
|
|
31
|
-
import {
|
|
27
|
+
import { neatoMode } from './start.js';
|
|
32
28
|
import { adjustNodesFull } from './fdp-adjust.js';
|
|
33
29
|
import { splineEdgesShifted, EDGETYPE_LINE } from './splines.js';
|
|
34
30
|
import { setEdgeTypeFromAttr } from '../dot/index.js';
|
|
35
31
|
import {
|
|
36
32
|
pccomps,
|
|
37
33
|
computeSubgraphBB,
|
|
34
|
+
getPack,
|
|
38
35
|
getPackInfo,
|
|
36
|
+
getPackModeInfo,
|
|
39
37
|
packGraphs,
|
|
40
38
|
PackMode,
|
|
41
39
|
type PackInfo,
|
|
@@ -43,7 +41,7 @@ import {
|
|
|
43
41
|
import { CL_OFFSET } from '../twopi/pipeline.js';
|
|
44
42
|
import { isACluster } from '../dot/rank.js';
|
|
45
43
|
import { doGraphLabel } from '../dot/graph-label.js';
|
|
46
|
-
import { graphInit
|
|
44
|
+
import { graphInit } from '../../common/graph-init.js';
|
|
47
45
|
import { placeGraphLabel } from '../dot/position-bbox.js';
|
|
48
46
|
import { gvPostprocess } from '../../common/postproc.js';
|
|
49
47
|
import { layoutMeasurer } from '../../common/nodeinit.js';
|
|
@@ -62,25 +60,14 @@ export {
|
|
|
62
60
|
// Mode parsing
|
|
63
61
|
// ---------------------------------------------------------------------------
|
|
64
62
|
|
|
65
|
-
/** Map from mode string to numeric constant. @see lib/neatogen/neato.h */
|
|
66
|
-
const MODE_MAP: Record<string, number> = {
|
|
67
|
-
KK: MODE_KK,
|
|
68
|
-
major: MODE_MAJOR,
|
|
69
|
-
hier: MODE_HIER,
|
|
70
|
-
ipsep: MODE_IPSEP,
|
|
71
|
-
sgd: MODE_SGD,
|
|
72
|
-
};
|
|
73
|
-
|
|
74
63
|
/**
|
|
75
|
-
* Parse `
|
|
76
|
-
* Defaults to MODE_MAJOR when unset
|
|
64
|
+
* Parse the root `mode` attribute into a numeric mode constant (C neatoMode).
|
|
65
|
+
* Defaults to MODE_MAJOR when unset; warns and defaults on an unknown value.
|
|
77
66
|
*
|
|
78
|
-
* @see lib/neatogen/
|
|
67
|
+
* @see lib/neatogen/neatoinit.c:neatoMode
|
|
79
68
|
*/
|
|
80
69
|
export function parseMode(g: Graph): number {
|
|
81
|
-
|
|
82
|
-
if (!s) return MODE_MAJOR;
|
|
83
|
-
return MODE_MAP[s] ?? MODE_MAJOR;
|
|
70
|
+
return neatoMode(g);
|
|
84
71
|
}
|
|
85
72
|
|
|
86
73
|
// ---------------------------------------------------------------------------
|
|
@@ -113,39 +100,15 @@ export function parseModel(g: Graph): number {
|
|
|
113
100
|
// ---------------------------------------------------------------------------
|
|
114
101
|
|
|
115
102
|
/**
|
|
116
|
-
*
|
|
103
|
+
* Remove node overlaps per the `overlap` attribute: C's removeOverlapWith
|
|
104
|
+
* dispatch (prism, scale family, oscale, ortho*, portho*, vpsc, voronoi) for
|
|
105
|
+
* every mode, as neato_layout's adjustNodes call does.
|
|
117
106
|
*
|
|
118
|
-
* @see lib/neatogen/neatoinit.c:neato_layout (
|
|
107
|
+
* @see lib/neatogen/neatoinit.c:neato_layout (adjustNodes call)
|
|
108
|
+
* @see lib/neatogen/adjust.c:removeOverlapWith
|
|
119
109
|
*/
|
|
120
110
|
export function maybeRemoveOverlap(g: Graph): void {
|
|
121
|
-
|
|
122
|
-
// overlap removal ("overlap: none"). VPSC runs only on request.
|
|
123
|
-
const overlap = g.attrs.get('overlap');
|
|
124
|
-
if (overlap === undefined || overlap === 'true') return;
|
|
125
|
-
// C removeOverlapWith dispatches by adjust mode. `overlap=false` resolves to
|
|
126
|
-
// AM_PRISM (getAdjustMode's boolean fallback lands on adjustMode[1] on a
|
|
127
|
-
// GTS+SFDP build), `overlap=scale/scalexy/compress` to the scale family, only
|
|
128
|
-
// `overlap=vpsc` to VPSC. Previously neato hardcoded VPSC for ALL of these,
|
|
129
|
-
// under-scaling every overlap=false graph (bb ~0.4-0.8x the oracle) and
|
|
130
|
-
// scaling scale-mode graphs wrong. adjustNodesFull is the ported
|
|
131
|
-
// removeOverlapWith body (PRISM via fdpAdjust + scale via scAdjust); VPSC is
|
|
132
|
-
// the one mode it does not cover. @see lib/neatogen/adjust.c:removeOverlapWith
|
|
133
|
-
if (overlap !== 'vpsc') {
|
|
134
|
-
adjustNodesFull(g);
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
const nodes = Array.from(g.nodes.values());
|
|
138
|
-
// Separation is DELIBERATELY the *default* nodesep, not GD_nodesep(g). C's
|
|
139
|
-
// overlap removal derives its padding from `sep`/DFLT_MARGIN (adjust.c:591-600
|
|
140
|
-
// sepFactor) and never reads GD_nodesep — that field is used only by
|
|
141
|
-
// makeSelfArcs (neatosplines.c:673) and routespl.c:1006. Before graph_init was
|
|
142
|
-
// consolidated, GD_nodesep was unset under neato at this point, so this site
|
|
143
|
-
// always saw the 18pt default; now that graphInit parses `nodesep` for every
|
|
144
|
-
// engine (as C does), reading it here would silently change the VPSC
|
|
145
|
-
// separation on graphs that set both `nodesep` and `overlap` (corpus: 1554,
|
|
146
|
-
// 2242) — a divergence C does not have. Pinned to the default it always used.
|
|
147
|
-
const nodesep = DEFAULT_NODESEP_POINTS / 72; // points → inches
|
|
148
|
-
removeOverlap(nodes, { x: nodesep / 2, y: nodesep / 2 });
|
|
111
|
+
adjustNodesFull(g);
|
|
149
112
|
}
|
|
150
113
|
|
|
151
114
|
// ---------------------------------------------------------------------------
|
|
@@ -205,7 +168,9 @@ export function neatoLayout(g: Graph): void {
|
|
|
205
168
|
|
|
206
169
|
// C uses pccomps (pin-aware): components with a pinned node are collected
|
|
207
170
|
// first (index 0) so the packer can hold them fixed. @see neatoinit.c:1391
|
|
208
|
-
const { graphs: comps, pinned } =
|
|
171
|
+
const { graphs: comps, pinned } = neatoPack(g, mode) >= 0
|
|
172
|
+
? pccomps(g, '_neato_cc')
|
|
173
|
+
: { graphs: [g], pinned: false };
|
|
209
174
|
if (comps.length > 1) {
|
|
210
175
|
layoutComponents(g, comps, mode, model, pinned);
|
|
211
176
|
} else {
|
|
@@ -227,6 +192,23 @@ export function neatoLayout(g: Graph): void {
|
|
|
227
192
|
gvPostprocess(g);
|
|
228
193
|
}
|
|
229
194
|
|
|
195
|
+
/**
|
|
196
|
+
* C's Pack decision: unset `pack` with no `packmode` packs (CL_OFFSET) unless
|
|
197
|
+
* layoutMode is MODE_KK (0); a set `packmode` always packs. Negative means the
|
|
198
|
+
* whole graph is laid out at once.
|
|
199
|
+
* @see lib/neatogen/neatoinit.c:1371-1383 (neato_layout)
|
|
200
|
+
*/
|
|
201
|
+
function neatoPack(g: Graph, layoutMode: number): number {
|
|
202
|
+
const pinfo: PackInfo = {
|
|
203
|
+
aspect: 1, sz: 0, margin: CL_OFFSET, doSplines: false,
|
|
204
|
+
mode: PackMode.Undef, fixed: null, vals: null, flags: 0,
|
|
205
|
+
};
|
|
206
|
+
const mode = getPackModeInfo(g, PackMode.Undef, pinfo);
|
|
207
|
+
const pack = getPack(g, -1, CL_OFFSET);
|
|
208
|
+
if (mode === PackMode.Undef) return pack < 0 && layoutMode !== MODE_KK ? CL_OFFSET : pack;
|
|
209
|
+
return pack < 0 ? CL_OFFSET : pack;
|
|
210
|
+
}
|
|
211
|
+
|
|
230
212
|
/**
|
|
231
213
|
* Register direct cluster subgraphs: build their labels and tight
|
|
232
214
|
* member bounding boxes for emission.
|
package/src/layout/neato/init.ts
CHANGED
|
@@ -21,7 +21,10 @@ import {
|
|
|
21
21
|
MODEL_SUBSET as STRESS_MODEL_SUBSET,
|
|
22
22
|
} from './stress.js';
|
|
23
23
|
import { sgdLayout } from './sgd.js';
|
|
24
|
-
import {
|
|
24
|
+
import { kkNeato } from './kk.js';
|
|
25
|
+
import { checkStart, assertSupported, parseStart, INIT_RANDOM, INIT_REGULAR } from './start.js';
|
|
26
|
+
|
|
27
|
+
export { checkStart };
|
|
25
28
|
import { mapbool } from '../dot/rank.js';
|
|
26
29
|
import {
|
|
27
30
|
computeApspPacked,
|
|
@@ -30,6 +33,7 @@ import {
|
|
|
30
33
|
OPT_EXP_FLAG,
|
|
31
34
|
} from './stress-kernel.js';
|
|
32
35
|
import { lateDouble } from '../../common/nodeinit.js';
|
|
36
|
+
import { inputscaleDivisor } from '../../common/utils-inputscale.js';
|
|
33
37
|
import { parseNeatoDrawing, neatoSetAspectRatio } from './set-aspect.js';
|
|
34
38
|
|
|
35
39
|
// ---------------------------------------------------------------------------
|
|
@@ -85,15 +89,10 @@ export function neatoInitNode(n: Node, dim = DFLT_DIM): void {
|
|
|
85
89
|
n.info.pos = pos;
|
|
86
90
|
}
|
|
87
91
|
if (n.info.UF_size === undefined) n.info.UF_size = 1;
|
|
88
|
-
// C neato_init_node has
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
// leave them at that unset-0 (no-measurer initNodeDefaults; poly-null
|
|
93
|
-
// custom/epsf shapes — C gives customs box geometry, a modeling gap
|
|
94
|
-
// flagged in the journal), so a fallback is still needed. The ONE case
|
|
95
|
-
// where 0 is a legitimate post-init size is shape=plain (poly_init
|
|
96
|
-
// IS_PLAIN, shapes.c:1962) — never clobber it.
|
|
92
|
+
// C neato_init_node has no size defaulting (neatoinit.c:60-66); the port's
|
|
93
|
+
// zero-initialised width/height need a fallback on two port-only paths
|
|
94
|
+
// (no-measurer initNodeDefaults, poly-null custom/epsf shapes). The one
|
|
95
|
+
// legitimate post-init 0 is shape=plain (poly_init IS_PLAIN) — never clobber it.
|
|
97
96
|
const plainShaped = (n.info.shape as ShapeDesc | undefined)?.name === 'plain';
|
|
98
97
|
if (!n.info.width && !plainShaped) n.info.width = 0.75;
|
|
99
98
|
if (!n.info.height && !plainShaped) n.info.height = 0.5;
|
|
@@ -109,10 +108,8 @@ export function neatoInitNode(n: Node, dim = DFLT_DIM): void {
|
|
|
109
108
|
* majorization starts from the input layout instead of a random init; a `!`
|
|
110
109
|
* suffix or `pin=true` additionally sets info.pinned (P_PIN → isFixed).
|
|
111
110
|
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
* (a `pos="27,42"` seeds (27,42), which orthog1 then centres). Must run AFTER
|
|
115
|
-
* neatoInitNode, which zeroes info.pos.
|
|
111
|
+
* `pos` is divided by PSinputscale when > 0 (neatoinit.c:90-95). Must run
|
|
112
|
+
* AFTER neatoInitNode, which zeroes info.pos.
|
|
116
113
|
* @see lib/neatogen/neatoinit.c:user_pos
|
|
117
114
|
*/
|
|
118
115
|
export function userPos(n: Node): boolean {
|
|
@@ -122,7 +119,8 @@ export function userPos(n: Node): boolean {
|
|
|
122
119
|
const num = '(-?\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?)';
|
|
123
120
|
const m = new RegExp(`^\\s*${num}\\s*,\\s*${num}(.?)`).exec(p);
|
|
124
121
|
if (m === null) return false; // C agerrorf: malformed pos, non-fatal
|
|
125
|
-
|
|
122
|
+
const div = inputscaleDivisor(n.root);
|
|
123
|
+
n.info.pos = [Number(m[1]) / div, Number(m[2]) / div];
|
|
126
124
|
n.info.posSet = true;
|
|
127
125
|
const pin = n.attrs.get('pin');
|
|
128
126
|
if (m[3] === '!' || (pin !== undefined && mapbool(pin))) n.info.pinned = true;
|
|
@@ -335,7 +333,7 @@ export function runMajorization(g: Graph, mode: number, model: number): void {
|
|
|
335
333
|
const haveLen = graphHasLen(g);
|
|
336
334
|
const vtx = makeGraphDataC(g, nodeList, haveLen);
|
|
337
335
|
checkStart(g); // C: srand48(seed) immediately before initLayout draws
|
|
338
|
-
const maxi =
|
|
336
|
+
const maxi = lateDouble(g.attrs.get('maxiter'), DFLT_ITERATIONS, 0);
|
|
339
337
|
const epsilon = lateDouble(g.attrs.get('epsilon'), 1e-4, 0); // DFLT_TOLERANCE
|
|
340
338
|
const Dij = haveLen ? computeWeightedApspPacked(vtx, n) : computeApspPacked(vtx, n);
|
|
341
339
|
const dCoords = [new Float64Array(n), new Float64Array(n)];
|
|
@@ -355,22 +353,6 @@ export function graphHasLen(g: Graph): boolean {
|
|
|
355
353
|
return false;
|
|
356
354
|
}
|
|
357
355
|
|
|
358
|
-
/**
|
|
359
|
-
* Seed the RNG from the start attr; no attr means the default seed 1.
|
|
360
|
-
* start=regular/self are not ported (no suite input uses them).
|
|
361
|
-
* @see lib/neatogen/neatoinit.c:setSeed
|
|
362
|
-
* @see lib/neatogen/neatoinit.c:checkStart
|
|
363
|
-
*/
|
|
364
|
-
export function checkStart(g: Graph): void {
|
|
365
|
-
const p = g.root.attrs.get('start');
|
|
366
|
-
let seed = 1;
|
|
367
|
-
if (p !== undefined && /^\d/.test(p)) {
|
|
368
|
-
const v = parseInt(p, 10);
|
|
369
|
-
if (!Number.isNaN(v)) seed = v;
|
|
370
|
-
}
|
|
371
|
-
srand48(seed);
|
|
372
|
-
}
|
|
373
|
-
|
|
374
356
|
/**
|
|
375
357
|
* Build the vtx_data adjacency in C's makeGraphData shape: per node,
|
|
376
358
|
* out-edges then in-edges (agfstedge order), self entry at index 0,
|
|
@@ -414,10 +396,13 @@ export function makeGraphDataC(g: Graph, nodeList: Node[], haveLen: boolean): Vt
|
|
|
414
396
|
*/
|
|
415
397
|
export function solveModel(g: Graph, mode: number, model: number): void {
|
|
416
398
|
if (g.nodes.size < 2) return;
|
|
417
|
-
|
|
418
|
-
if (mode ===
|
|
419
|
-
|
|
420
|
-
|
|
399
|
+
assertSupported(g, mode, model);
|
|
400
|
+
if (mode === MODE_KK) return kkNeato(g, g.nodes.size, model);
|
|
401
|
+
if (mode === MODE_SGD) {
|
|
402
|
+
// C initial_positions -> checkStart: regular positions are kept
|
|
403
|
+
if (parseStart(g.root.attrs.get('start'), INIT_RANDOM).init === INIT_REGULAR) checkStart(g);
|
|
404
|
+
sgdLayout(g, model);
|
|
405
|
+
return;
|
|
421
406
|
}
|
|
422
407
|
runMajorization(g, mode, model);
|
|
423
408
|
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EPL-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Kamada-Kawai ideal-distance setup: Dijkstra over edge lengths with C's
|
|
4
|
+
* binary heap (`neato_enqueue`/`neato_dequeue`, `shortest_path`, `s1`,
|
|
5
|
+
* `make_spring`) and `mds_model`.
|
|
6
|
+
*
|
|
7
|
+
* The C keeps the heap, its size and the source node in file-statics and the
|
|
8
|
+
* per-node `ND_dist`/`ND_hops`/`ND_heapindex` in node records; here they live
|
|
9
|
+
* in one {@link PathState} per run (no module state), indexed by `ND_id`.
|
|
10
|
+
*
|
|
11
|
+
* @see lib/neatogen/stuff.c
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** One neighbour of a node: its id and the edge's `ED_dist`. */
|
|
15
|
+
export interface Neighbour {
|
|
16
|
+
readonly id: number;
|
|
17
|
+
readonly len: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Heap + per-node Dijkstra state. @see lib/neatogen/stuff.c (Heap, Heapsize) */
|
|
21
|
+
export interface PathState {
|
|
22
|
+
readonly n: number;
|
|
23
|
+
/** `GD_dist` as a flat n*n matrix. */
|
|
24
|
+
readonly dist: Float64Array;
|
|
25
|
+
/** `agfstedge` neighbours of each node, in C's iteration order. */
|
|
26
|
+
readonly adj: readonly (readonly Neighbour[])[];
|
|
27
|
+
/** `Initial_dist`. */
|
|
28
|
+
readonly initialDist: number;
|
|
29
|
+
readonly heap: Int32Array;
|
|
30
|
+
heapSize: number;
|
|
31
|
+
/** `ND_heapindex` (-1 = not queued). */
|
|
32
|
+
readonly heapIndex: Int32Array;
|
|
33
|
+
/** `ND_dist`. */
|
|
34
|
+
readonly nd: Float64Array;
|
|
35
|
+
/** `ND_hops`. */
|
|
36
|
+
readonly hops: Int32Array;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Fresh path state for `n` nodes. @see lib/neatogen/stuff.c:shortest_path */
|
|
40
|
+
export function newPathState(
|
|
41
|
+
n: number, dist: Float64Array, adj: readonly (readonly Neighbour[])[], initialDist: number,
|
|
42
|
+
): PathState {
|
|
43
|
+
return {
|
|
44
|
+
n, dist, adj, initialDist,
|
|
45
|
+
heap: new Int32Array(n + 1), heapSize: 0,
|
|
46
|
+
heapIndex: new Int32Array(n).fill(-1),
|
|
47
|
+
nd: new Float64Array(n), hops: new Int32Array(n),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** @see lib/neatogen/stuff.c:heapup */
|
|
52
|
+
function heapUp(s: PathState, v: number): void {
|
|
53
|
+
for (let i = s.heapIndex[v]!, par: number; i > 0; i = par) {
|
|
54
|
+
par = (i - 1) >> 1;
|
|
55
|
+
const u = s.heap[par]!;
|
|
56
|
+
if (s.nd[u]! <= s.nd[v]!) break;
|
|
57
|
+
s.heap[par] = v;
|
|
58
|
+
s.heapIndex[v] = par;
|
|
59
|
+
s.heap[i] = u;
|
|
60
|
+
s.heapIndex[u] = i;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** @see lib/neatogen/stuff.c:heapdown */
|
|
65
|
+
function heapDown(s: PathState, v: number): void {
|
|
66
|
+
let i = s.heapIndex[v]!;
|
|
67
|
+
for (let left = 2 * i + 1; left < s.heapSize; left = 2 * i + 1) {
|
|
68
|
+
const right = left + 1;
|
|
69
|
+
const c = right < s.heapSize && s.nd[s.heap[right]!]! < s.nd[s.heap[left]!]! ? right : left;
|
|
70
|
+
const u = s.heap[c]!;
|
|
71
|
+
if (s.nd[v]! <= s.nd[u]!) break;
|
|
72
|
+
s.heap[c] = v;
|
|
73
|
+
s.heapIndex[v] = c;
|
|
74
|
+
s.heap[i] = u;
|
|
75
|
+
s.heapIndex[u] = i;
|
|
76
|
+
i = c;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** @see lib/neatogen/stuff.c:neato_enqueue */
|
|
81
|
+
function enqueue(s: PathState, v: number): void {
|
|
82
|
+
const i = s.heapSize++;
|
|
83
|
+
s.heapIndex[v] = i;
|
|
84
|
+
s.heap[i] = v;
|
|
85
|
+
if (i > 0) heapUp(s, v);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Returns -1 when empty (C: NULL). @see lib/neatogen/stuff.c:neato_dequeue */
|
|
89
|
+
function dequeue(s: PathState): number {
|
|
90
|
+
if (s.heapSize === 0) return -1;
|
|
91
|
+
const rv = s.heap[0]!;
|
|
92
|
+
const i = --s.heapSize;
|
|
93
|
+
const v = s.heap[i]!;
|
|
94
|
+
s.heap[0] = v;
|
|
95
|
+
s.heapIndex[v] = 0;
|
|
96
|
+
if (i > 1) heapDown(s, v);
|
|
97
|
+
s.heapIndex[rv] = -1;
|
|
98
|
+
return rv;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** @see lib/neatogen/stuff.c:make_spring */
|
|
102
|
+
function makeSpring(s: PathState, u: number, v: number, f: number): void {
|
|
103
|
+
s.dist[u * s.n + v] = f;
|
|
104
|
+
s.dist[v * s.n + u] = f;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Single-source shortest paths from `src`. @see lib/neatogen/stuff.c:s1 */
|
|
108
|
+
function s1(s: PathState, src: number): void {
|
|
109
|
+
s.nd.fill(s.initialDist);
|
|
110
|
+
s.nd[src] = 0;
|
|
111
|
+
s.hops[src] = 0;
|
|
112
|
+
enqueue(s, src);
|
|
113
|
+
for (let v = dequeue(s); v >= 0; v = dequeue(s)) {
|
|
114
|
+
if (v !== src) makeSpring(s, src, v, s.nd[v]!);
|
|
115
|
+
for (const { id: u, len } of s.adj[v]!) {
|
|
116
|
+
const f = s.nd[v]! + len;
|
|
117
|
+
if (s.nd[u]! <= f) continue;
|
|
118
|
+
s.nd[u] = f;
|
|
119
|
+
if (s.heapIndex[u]! >= 0) heapUp(s, u);
|
|
120
|
+
else {
|
|
121
|
+
s.hops[u] = s.hops[v]! + 1;
|
|
122
|
+
enqueue(s, u);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** All-pairs shortest paths into `GD_dist`. @see lib/neatogen/stuff.c:shortest_path */
|
|
129
|
+
export function shortestPath(s: PathState): void {
|
|
130
|
+
for (let v = 0; v < s.n; v++) s1(s, v);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Overwrite `GD_dist` with edge lengths. C indexes the matrix with `AGSEQ`
|
|
135
|
+
* (1-based), so the last node's row falls outside the n*n allocation (heap
|
|
136
|
+
* overflow in C); out-of-range writes are dropped here.
|
|
137
|
+
* @see lib/neatogen/neatoinit.c:mds_model
|
|
138
|
+
*/
|
|
139
|
+
export function mdsModel(
|
|
140
|
+
dist: Float64Array, n: number, edges: readonly { tailSeq: number; headSeq: number; len: number }[],
|
|
141
|
+
): void {
|
|
142
|
+
for (const { tailSeq: i, headSeq: j, len } of edges) {
|
|
143
|
+
if (i === j) continue;
|
|
144
|
+
for (const k of [i * n + j, j * n + i]) if (k < dist.length) dist[k] = len;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EPL-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Gaussian elimination with partial pivoting for the small Newton systems of
|
|
4
|
+
* Kamada-Kawai (`move_node`).
|
|
5
|
+
*
|
|
6
|
+
* @see lib/neatogen/solve.c
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Pivots below this make the system ill-conditioned. @see solve.c:solve */
|
|
10
|
+
const MIN_PIVOT = 1.0e-10;
|
|
11
|
+
|
|
12
|
+
/** @see lib/neatogen/solve.c:SWAP */
|
|
13
|
+
function swap(x: Float64Array, i: number, j: number): void {
|
|
14
|
+
const t = x[i]!;
|
|
15
|
+
x[i] = x[j]!;
|
|
16
|
+
x[j] = t;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Returns false on an unusable pivot. Eliminates unknown `i`. */
|
|
20
|
+
function eliminate(a: Float64Array, c: Float64Array, n: number, i: number): boolean {
|
|
21
|
+
let amax = 0.0;
|
|
22
|
+
let istar = 0;
|
|
23
|
+
for (let ii = i; ii < n; ii++) {
|
|
24
|
+
const dum = Math.abs(a[ii * n + i]!);
|
|
25
|
+
if (dum < amax) continue;
|
|
26
|
+
istar = ii;
|
|
27
|
+
amax = dum;
|
|
28
|
+
}
|
|
29
|
+
if (amax < MIN_PIVOT) return false;
|
|
30
|
+
for (let j = i; j < n; j++) swap(a, istar * n + j, i * n + j);
|
|
31
|
+
swap(c, istar, i);
|
|
32
|
+
for (let ii = i + 1; ii < n; ii++) {
|
|
33
|
+
const pivot = a[ii * n + i]! / a[i * n + i]!;
|
|
34
|
+
c[ii]! -= pivot * c[i]!;
|
|
35
|
+
for (let j = 0; j < n; j++) a[ii * n + j] = a[ii * n + j]! - pivot * a[i * n + j]!;
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Solve a*b = c (a is n*n row-major). On success `a` and `c` are restored and
|
|
42
|
+
* `b` holds the solution; when ill-conditioned `b` is untouched. C prints
|
|
43
|
+
* "ill-conditioned" to stdout; a library has no stdout, so it is a warning.
|
|
44
|
+
* @see lib/neatogen/solve.c:solve
|
|
45
|
+
*/
|
|
46
|
+
export function solve(a: Float64Array, b: Float64Array, c: Float64Array, n: number): void {
|
|
47
|
+
const asave = Float64Array.from(a);
|
|
48
|
+
const csave = Float64Array.from(c);
|
|
49
|
+
let ok = true;
|
|
50
|
+
for (let i = 0; i < n - 1 && ok; i++) ok = eliminate(a, c, n, i);
|
|
51
|
+
if (!ok || Math.abs(a[n * n - 1]!) < MIN_PIVOT) {
|
|
52
|
+
console.warn('ill-conditioned');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
b[n - 1] = c[n - 1]! / a[n * n - 1]!;
|
|
56
|
+
for (let k = 0; k < n - 1; k++) {
|
|
57
|
+
const m = n - k - 2;
|
|
58
|
+
b[m] = c[m]!;
|
|
59
|
+
for (let j = m + 1; j < n; j++) b[m]! -= a[m * n + j]! * b[j]!;
|
|
60
|
+
b[m]! /= a[m * n + m]!;
|
|
61
|
+
}
|
|
62
|
+
c.set(csave);
|
|
63
|
+
a.set(asave);
|
|
64
|
+
}
|