@knowvah/dot-engine 2.0.0 → 2.0.2
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/async/collect.d.ts.map +1 -1
- package/dist/async/render-async.d.ts.map +1 -1
- package/dist/gvc/device-labels.d.ts +30 -0
- package/dist/gvc/device-labels.d.ts.map +1 -0
- package/dist/gvc/device-transform.d.ts +22 -0
- package/dist/gvc/device-transform.d.ts.map +1 -0
- package/dist/gvc/device.d.ts +2 -42
- package/dist/gvc/device.d.ts.map +1 -1
- package/dist/index.js +174 -137
- package/dist/index.js.map +4 -4
- package/dist/layout/dot/graph-label.d.ts +10 -0
- package/dist/layout/dot/graph-label.d.ts.map +1 -1
- package/dist/layout/fdp/normalize.d.ts +2 -1
- package/dist/layout/fdp/normalize.d.ts.map +1 -1
- package/dist/layout/fdp/xlayout.d.ts +1 -1
- package/dist/layout/fdp/xlayout.d.ts.map +1 -1
- package/dist/layout/neato/dijkstra.d.ts +0 -32
- package/dist/layout/neato/dijkstra.d.ts.map +1 -1
- package/dist/layout/neato/fdp-adjust.d.ts +14 -3
- package/dist/layout/neato/fdp-adjust.d.ts.map +1 -1
- package/dist/layout/neato/index.d.ts +5 -5
- package/dist/layout/neato/index.d.ts.map +1 -1
- package/dist/layout/neato/sc-adjust.d.ts +2 -9
- package/dist/layout/neato/sc-adjust.d.ts.map +1 -1
- package/dist/layout/neato/simple-scale.d.ts +15 -0
- package/dist/layout/neato/simple-scale.d.ts.map +1 -0
- package/dist/layout/sfdp/index.d.ts.map +1 -1
- package/dist/render/public.d.ts +5 -0
- package/dist/render/public.d.ts.map +1 -1
- package/dist/render.js +171 -134
- package/dist/render.js.map +4 -4
- package/package.json +1 -1
- package/src/async/collect.ts +2 -14
- package/src/async/render-async.ts +1 -9
- package/src/gvc/device-labels.ts +98 -0
- package/src/gvc/device-transform.ts +58 -0
- package/src/gvc/device.ts +8 -132
- package/src/layout/dot/graph-label.ts +6 -1
- package/src/layout/fdp/normalize.ts +12 -8
- package/src/layout/fdp/xlayout.ts +8 -6
- package/src/layout/neato/dijkstra.ts +0 -141
- package/src/layout/neato/fdp-adjust.ts +33 -8
- package/src/layout/neato/index.ts +13 -12
- package/src/layout/neato/sc-adjust.ts +2 -16
- package/src/layout/neato/simple-scale.ts +54 -0
- package/src/layout/sfdp/index.ts +2 -1
- package/src/render/public.ts +6 -5
- package/dist/layout/neato/overlap.d.ts +0 -85
- package/dist/layout/neato/overlap.d.ts.map +0 -1
- package/src/layout/neato/overlap.ts +0 -158
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EPL-2.0
|
|
2
2
|
/** @see lib/neatogen/dijkstra.c */
|
|
3
3
|
|
|
4
|
-
import type { GraphSgd } from './sgd.js';
|
|
5
|
-
|
|
6
4
|
/**
|
|
7
5
|
* Sparse-graph vertex descriptor used by neato layout algorithms.
|
|
8
6
|
* Matches `vtx_data` in lib/neatogen/sparsegraph.h.
|
|
@@ -31,9 +29,6 @@ export interface TermSgd {
|
|
|
31
29
|
w: number; // weight = 1/d²
|
|
32
30
|
}
|
|
33
31
|
|
|
34
|
-
/** Sentinel used for unvisited BFS nodes (matches C INT_MAX). */
|
|
35
|
-
const BFS_INF = 0x7fffffff;
|
|
36
|
-
|
|
37
32
|
// ---------------------------------------------------------------------------
|
|
38
33
|
// Min-heap (keyed by Float32Array distances)
|
|
39
34
|
// ---------------------------------------------------------------------------
|
|
@@ -158,139 +153,3 @@ export function dijkstra(
|
|
|
158
153
|
dijkstraRelax(closest, closestDist, graph, dist, heap);
|
|
159
154
|
}
|
|
160
155
|
}
|
|
161
|
-
|
|
162
|
-
// ---------------------------------------------------------------------------
|
|
163
|
-
// Public: All-pairs shortest paths (packed upper-triangular)
|
|
164
|
-
// ---------------------------------------------------------------------------
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* BFS single-source distances for unweighted graphs.
|
|
168
|
-
* Fills `dist` with hop counts; unreachable nodes get BFS_INF (0x7fffffff).
|
|
169
|
-
* edges[0] of each vertex is the self-loop and is skipped.
|
|
170
|
-
* @see lib/neatogen/bfs.c:bfs
|
|
171
|
-
*/
|
|
172
|
-
export function bfsDistances(
|
|
173
|
-
src: number,
|
|
174
|
-
graph: VtxData[],
|
|
175
|
-
n: number,
|
|
176
|
-
dist: Float32Array,
|
|
177
|
-
): void {
|
|
178
|
-
for (let i = 0; i < n; i++) dist[i] = BFS_INF;
|
|
179
|
-
dist[src] = 0;
|
|
180
|
-
const queue = new Int32Array(n);
|
|
181
|
-
let head = 0, tail = 0;
|
|
182
|
-
queue[tail++] = src;
|
|
183
|
-
while (head < tail) {
|
|
184
|
-
const u = queue[head++];
|
|
185
|
-
const d = dist[u] + 1;
|
|
186
|
-
const vtx = graph[u];
|
|
187
|
-
for (let e = 1; e < vtx.nedges; e++) {
|
|
188
|
-
const v = vtx.edges[e];
|
|
189
|
-
if (dist[v] === BFS_INF) { dist[v] = d; queue[tail++] = v; }
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* All-pairs shortest paths (unweighted), packed upper-triangular.
|
|
196
|
-
* Length n*(n+1)/2; index for (i,j) j>=i: i*(2n-i-1)/2 + j.
|
|
197
|
-
* @see lib/neatogen/stress.c:compute_apsp_packed
|
|
198
|
-
*/
|
|
199
|
-
export function computeApspPacked(graph: VtxData[], n: number): Float32Array {
|
|
200
|
-
const result = new Float32Array(n * (n + 1) / 2);
|
|
201
|
-
const di = new Float32Array(n);
|
|
202
|
-
let count = 0;
|
|
203
|
-
for (let i = 0; i < n; i++) {
|
|
204
|
-
bfsDistances(i, graph, n, di);
|
|
205
|
-
for (let j = i; j < n; j++) result[count++] = di[j];
|
|
206
|
-
}
|
|
207
|
-
return result;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* All-pairs shortest paths (weighted), packed upper-triangular.
|
|
212
|
-
* Length n*(n+1)/2.
|
|
213
|
-
* @see lib/neatogen/stress.c:compute_weighted_apsp_packed
|
|
214
|
-
*/
|
|
215
|
-
export function computeWeightedApspPacked(
|
|
216
|
-
graph: VtxData[],
|
|
217
|
-
n: number,
|
|
218
|
-
): Float32Array {
|
|
219
|
-
const result = new Float32Array(n * (n + 1) / 2);
|
|
220
|
-
const di = new Float32Array(n);
|
|
221
|
-
let count = 0;
|
|
222
|
-
for (let i = 0; i < n; i++) {
|
|
223
|
-
dijkstra(i, graph, n, di);
|
|
224
|
-
for (let j = i; j < n; j++) result[count++] = di[j];
|
|
225
|
-
}
|
|
226
|
-
return result;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// ---------------------------------------------------------------------------
|
|
230
|
-
// dijkstraSgd helpers
|
|
231
|
-
// ---------------------------------------------------------------------------
|
|
232
|
-
|
|
233
|
-
/** Initialise SGD dists from source's adjacency list. @internal */
|
|
234
|
-
export function sgdInitDist(
|
|
235
|
-
graph: GraphSgd,
|
|
236
|
-
src: number,
|
|
237
|
-
dists: Float32Array,
|
|
238
|
-
): void {
|
|
239
|
-
dists[src] = 0;
|
|
240
|
-
const end = graph.sources[src + 1];
|
|
241
|
-
for (let i = graph.sources[src]; i < end; i++) {
|
|
242
|
-
dists[graph.targets[i]] = graph.weights[i];
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/** Relax SGD neighbours of `closest` and push updates onto `heap`. @internal */
|
|
247
|
-
export function sgdRelaxNeighbors(
|
|
248
|
-
graph: GraphSgd,
|
|
249
|
-
closest: number,
|
|
250
|
-
d: number,
|
|
251
|
-
dists: Float32Array,
|
|
252
|
-
heap: number[],
|
|
253
|
-
): void {
|
|
254
|
-
const end = graph.sources[closest + 1];
|
|
255
|
-
for (let i = graph.sources[closest]; i < end; i++) {
|
|
256
|
-
const target = graph.targets[i];
|
|
257
|
-
const nd = d + graph.weights[i];
|
|
258
|
-
if (nd < dists[target]) {
|
|
259
|
-
dists[target] = nd;
|
|
260
|
-
heapPush(heap, dists, target);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// ---------------------------------------------------------------------------
|
|
266
|
-
// Public: dijkstraSgd
|
|
267
|
-
// ---------------------------------------------------------------------------
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Single-source shortest paths for the SGD engine.
|
|
271
|
-
* Writes terms for all reachable `j < src` and pinned nodes.
|
|
272
|
-
* Returns the count of terms written.
|
|
273
|
-
*
|
|
274
|
-
* @see lib/neatogen/dijkstra.c:dijkstra_sgd
|
|
275
|
-
*/
|
|
276
|
-
export function dijkstraSgd(
|
|
277
|
-
graph: GraphSgd,
|
|
278
|
-
src: number,
|
|
279
|
-
terms: TermSgd[],
|
|
280
|
-
): number {
|
|
281
|
-
const n = graph.n;
|
|
282
|
-
const dists = new Float32Array(n).fill(Infinity);
|
|
283
|
-
sgdInitDist(graph, src, dists);
|
|
284
|
-
const heap = heapBuildExcluding(src, n, dists);
|
|
285
|
-
let offset = 0;
|
|
286
|
-
while (heap.length > 0) {
|
|
287
|
-
const closest = heapPop(heap, dists);
|
|
288
|
-
const d = dists[closest];
|
|
289
|
-
if (!isFinite(d)) break;
|
|
290
|
-
if (graph.pinneds[closest] || closest < src) {
|
|
291
|
-
terms[offset++] = { i: src, j: closest, d, w: 1 / (d * d) };
|
|
292
|
-
}
|
|
293
|
-
sgdRelaxNeighbors(graph, closest, d, dists, heap);
|
|
294
|
-
}
|
|
295
|
-
return offset;
|
|
296
|
-
}
|
|
@@ -18,6 +18,8 @@ import {
|
|
|
18
18
|
} from '../sfdp/sparse-matrix.js';
|
|
19
19
|
import { removeOverlapPrism } from './overlap-prism.js';
|
|
20
20
|
import { scAdjust } from './sc-adjust.js';
|
|
21
|
+
import { simpleScale } from './simple-scale.js';
|
|
22
|
+
import { normalizeG } from '../fdp/normalize.js';
|
|
21
23
|
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
24
|
import { countOverlapIn, sAdjustGraph } from './adjust-info.js';
|
|
23
25
|
import { vpscAdjust } from './vpsc-adjust.js';
|
|
@@ -149,12 +151,26 @@ const ADJUST_MODES = new Map<string, readonly [number, string]>([
|
|
|
149
151
|
['porthoyx', [AM_PORTHOYX, 'yx pseudo-orthogonal constraints']],
|
|
150
152
|
]);
|
|
151
153
|
|
|
154
|
+
/**
|
|
155
|
+
* getAdjustMode's fallback warning: a value that is not a mode name, not
|
|
156
|
+
* prism*, and not a boolean is used as `false` (prism) with this warning.
|
|
157
|
+
* @see lib/neatogen/adjust.c:getAdjustMode (unmappable branch)
|
|
158
|
+
*/
|
|
159
|
+
export function warnUnrecognizedOverlap(flag: string | undefined): void {
|
|
160
|
+
if (flag === undefined || flag === '') return;
|
|
161
|
+
const s = flag.toLowerCase();
|
|
162
|
+
if (s.startsWith('prism') || NAMED_MODES.has(s)) return;
|
|
163
|
+
if (mapBoolDflt(flag, false) === mapBoolDflt(flag, true)) return;
|
|
164
|
+
console.warn(`Unrecognized overlap value "${flag}" - using false`);
|
|
165
|
+
}
|
|
166
|
+
|
|
152
167
|
/**
|
|
153
168
|
* Resolve an `overlap` value to its adjust mode (the PRISM try count lands in
|
|
154
|
-
* `value`).
|
|
169
|
+
* `value`), warning as C does for an unrecognized value.
|
|
155
170
|
* @see lib/neatogen/adjust.c:getAdjustMode
|
|
156
171
|
*/
|
|
157
172
|
export function getAdjustMode(flag: string | undefined): AdjustData {
|
|
173
|
+
warnUnrecognizedOverlap(flag);
|
|
158
174
|
const ntry = overlapPrismTries(flag);
|
|
159
175
|
if (ntry !== null) return { mode: AM_PRISM, print: 'prism', value: ntry };
|
|
160
176
|
const named = ADJUST_MODES.get((flag ?? '').toLowerCase());
|
|
@@ -208,15 +224,16 @@ function adjustVoronoi(g: Graph, am: AdjustData): number {
|
|
|
208
224
|
|
|
209
225
|
/**
|
|
210
226
|
* Use `am` to decide if and how to remove node overlaps. normalize and
|
|
211
|
-
* simpleScale
|
|
212
|
-
*
|
|
227
|
+
* simpleScale run first, whatever the mode (the `normalize` / `scale`
|
|
228
|
+
* attributes); returns non-zero if nodes moved.
|
|
213
229
|
* @see lib/neatogen/adjust.c:removeOverlapWith
|
|
214
230
|
*/
|
|
215
231
|
export function removeOverlapWith(g: Graph, am: AdjustData): number {
|
|
216
232
|
if (g.nodes.size < 2) return 0;
|
|
217
|
-
|
|
218
|
-
if (am.mode
|
|
219
|
-
|
|
233
|
+
const nret = normalizeG(g) + simpleScale(g);
|
|
234
|
+
if (am.mode === AM_NONE) return nret;
|
|
235
|
+
if (am.mode > AM_SCALE) return nret + adjustLate(g, am);
|
|
236
|
+
return nret + (am.mode === AM_SCALE ? sAdjustGraph(g) : adjustVoronoi(g, am));
|
|
220
237
|
}
|
|
221
238
|
|
|
222
239
|
/**
|
|
@@ -224,6 +241,14 @@ export function removeOverlapWith(g: Graph, am: AdjustData): number {
|
|
|
224
241
|
* @see lib/neatogen/adjust.c:adjustNodes / removeOverlapAs
|
|
225
242
|
*/
|
|
226
243
|
export function adjustNodesFull(g: Graph): number {
|
|
227
|
-
|
|
228
|
-
return removeOverlapWith(g,
|
|
244
|
+
if (g.nodes.size < 2) return 0; // removeOverlapAs returns before getAdjustMode
|
|
245
|
+
return removeOverlapWith(g, graphAdjustMode(g));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* The graph's `overlap` value resolved once (agget falls back to the root).
|
|
250
|
+
* @see lib/neatogen/adjust.c:graphAdjustMode
|
|
251
|
+
*/
|
|
252
|
+
export function graphAdjustMode(g: Graph): AdjustData {
|
|
253
|
+
return getAdjustMode(g.attrs.get('overlap') ?? g.root.attrs.get('overlap'));
|
|
229
254
|
}
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
MODEL_MDS,
|
|
26
26
|
} from './init.js';
|
|
27
27
|
import { neatoMode } from './start.js';
|
|
28
|
-
import {
|
|
28
|
+
import { removeOverlapWith, graphAdjustMode, type AdjustData } from './fdp-adjust.js';
|
|
29
29
|
import { splineEdgesShifted, EDGETYPE_LINE } from './splines.js';
|
|
30
30
|
import { setEdgeTypeFromAttr } from '../dot/index.js';
|
|
31
31
|
import {
|
|
@@ -100,15 +100,14 @@ export function parseModel(g: Graph): number {
|
|
|
100
100
|
// ---------------------------------------------------------------------------
|
|
101
101
|
|
|
102
102
|
/**
|
|
103
|
-
* Remove node overlaps
|
|
104
|
-
*
|
|
105
|
-
* every mode, as neato_layout's adjustNodes call does.
|
|
103
|
+
* Remove node overlaps with the mode neato_layout resolved once
|
|
104
|
+
* (graphAdjustMode): C's removeOverlapWith dispatch for every mode.
|
|
106
105
|
*
|
|
107
|
-
* @see lib/neatogen/neatoinit.c:neato_layout (
|
|
106
|
+
* @see lib/neatogen/neatoinit.c:neato_layout (removeOverlapWith calls)
|
|
108
107
|
* @see lib/neatogen/adjust.c:removeOverlapWith
|
|
109
108
|
*/
|
|
110
|
-
export function maybeRemoveOverlap(g: Graph): void {
|
|
111
|
-
|
|
109
|
+
export function maybeRemoveOverlap(g: Graph, am: AdjustData): void {
|
|
110
|
+
removeOverlapWith(g, am);
|
|
112
111
|
}
|
|
113
112
|
|
|
114
113
|
// ---------------------------------------------------------------------------
|
|
@@ -164,6 +163,8 @@ export function neatoLayout(g: Graph): void {
|
|
|
164
163
|
}
|
|
165
164
|
|
|
166
165
|
const mode = parseMode(g);
|
|
166
|
+
// C resolves the overlap mode once, before any component. @see neatoinit.c:1369
|
|
167
|
+
const am = graphAdjustMode(g);
|
|
167
168
|
const model = parseModel(g);
|
|
168
169
|
|
|
169
170
|
// C uses pccomps (pin-aware): components with a pinned node are collected
|
|
@@ -172,10 +173,10 @@ export function neatoLayout(g: Graph): void {
|
|
|
172
173
|
? pccomps(g, '_neato_cc')
|
|
173
174
|
: { graphs: [g], pinned: false };
|
|
174
175
|
if (comps.length > 1) {
|
|
175
|
-
layoutComponents(g, comps, mode, model, pinned);
|
|
176
|
+
layoutComponents(g, comps, { mode, model, am }, pinned);
|
|
176
177
|
} else {
|
|
177
178
|
solveModel(g, mode, model); // srand48 happens inside (C checkStart)
|
|
178
|
-
maybeRemoveOverlap(g);
|
|
179
|
+
maybeRemoveOverlap(g, am);
|
|
179
180
|
// C: spline_edges shifts pos to the origin, syncs coord, routes.
|
|
180
181
|
splineEdgesShifted(g);
|
|
181
182
|
}
|
|
@@ -236,13 +237,13 @@ function addClusters(g: Graph): void {
|
|
|
236
237
|
* @see lib/neatogen/neatoinit.c:neato_layout (Pack >= 0 branch)
|
|
237
238
|
*/
|
|
238
239
|
function layoutComponents(
|
|
239
|
-
g: Graph, comps: Graph[], mode: number
|
|
240
|
+
g: Graph, comps: Graph[], solve: { mode: number; model: number; am: AdjustData }, pinned: boolean,
|
|
240
241
|
): void {
|
|
241
242
|
for (const gc of comps) {
|
|
242
243
|
// @see lib/neatogen/neatoinit.c:1398 (setEdgeType FUNCTION, per component)
|
|
243
244
|
setEdgeTypeFromAttr(gc, EDGETYPE_LINE);
|
|
244
|
-
solveModel(gc, mode, model);
|
|
245
|
-
maybeRemoveOverlap(gc);
|
|
245
|
+
solveModel(gc, solve.mode, solve.model);
|
|
246
|
+
maybeRemoveOverlap(gc, solve.am);
|
|
246
247
|
splineEdgesShifted(gc);
|
|
247
248
|
}
|
|
248
249
|
// C sets fixed[0]=true when a pinned node is present: pccomps merges every
|
|
@@ -11,9 +11,8 @@
|
|
|
11
11
|
* Using Constrained Optimization", Constraints 8(2):143-172, 2003 (per the
|
|
12
12
|
* C source comment).
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* no-op (normalize/simpleScale only fire on rare attrs, also unported).
|
|
14
|
+
* Dispatched from removeOverlapWith (fdp-adjust.ts) for overlap=scale,
|
|
15
|
+
* scalexy and compress.
|
|
17
16
|
*
|
|
18
17
|
* @see lib/neatogen/constraint.c:scAdjust
|
|
19
18
|
* @see lib/neatogen/adjust.c:removeOverlapWith / getAdjustMode
|
|
@@ -191,16 +190,3 @@ export function scAdjust(g: Graph, equal: number): number {
|
|
|
191
190
|
return 1;
|
|
192
191
|
}
|
|
193
192
|
|
|
194
|
-
/**
|
|
195
|
-
* The adjustNodes overlap-mode subset reaching scAdjust; other modes are
|
|
196
|
-
* no-ops (see module doc).
|
|
197
|
-
* @see lib/neatogen/adjust.c:999 adjustNodes, :776 adjustMode table
|
|
198
|
-
*/
|
|
199
|
-
export function adjustNodesScale(g: Graph): number {
|
|
200
|
-
// agget falls back to the root graph's attrs for component subgraphs.
|
|
201
|
-
const flag = (g.attrs.get('overlap') ?? g.root.attrs.get('overlap') ?? '').toLowerCase();
|
|
202
|
-
if (flag === 'scale') return scAdjust(g, 1); // AM_NSCALE
|
|
203
|
-
if (flag === 'scalexy') return scAdjust(g, 0); // AM_SCALEXY
|
|
204
|
-
if (flag === 'compress') return scAdjust(g, -1); // AM_COMPRESS
|
|
205
|
-
return 0; // unset -> AM_NONE; other modes unported
|
|
206
|
-
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EPL-2.0
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The `scale` graph attribute: multiply every node position by (sx, sy)
|
|
5
|
+
* before overlap removal. removeOverlapWith runs it right after normalize,
|
|
6
|
+
* whatever the overlap mode.
|
|
7
|
+
*
|
|
8
|
+
* @see lib/neatogen/adjust.c:simpleScale
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { Graph } from '../../model/graph.js';
|
|
12
|
+
import { aggetGraph } from '../fdp/fdp-model.js';
|
|
13
|
+
|
|
14
|
+
/** @see lib/neatogen/adjust.c:ISZERO */
|
|
15
|
+
const ISZERO = (d: number): boolean => Math.abs(d) < 0.000000001;
|
|
16
|
+
|
|
17
|
+
/** One C `%lf` conversion: optional leading whitespace, then a strtod number. */
|
|
18
|
+
const LF = /^\s*([+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?)/;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* C `sscanf(p, "%lf,%lf", &x, &y)`: the values converted, in order (0, 1 or
|
|
22
|
+
* 2 of them). An empty string returns none — C's EOF (-1) there reads an
|
|
23
|
+
* uninitialised pointf, undefined behaviour the port defines as a no-op.
|
|
24
|
+
*/
|
|
25
|
+
function scanPair(p: string): number[] {
|
|
26
|
+
const m1 = LF.exec(p);
|
|
27
|
+
if (m1 === null) return [];
|
|
28
|
+
const rest = p.slice(m1[0].length);
|
|
29
|
+
if (!rest.startsWith(',')) return [parseFloat(m1[1]!)];
|
|
30
|
+
const m2 = LF.exec(rest.slice(1));
|
|
31
|
+
return m2 === null ? [parseFloat(m1[1]!)] : [parseFloat(m1[1]!), parseFloat(m2[1]!)];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Scale node positions by the `scale` attribute ("s" or "sx,sy"). Returns 1
|
|
36
|
+
* if nodes moved, else 0 (unset, unparsable, a zero factor, or 1,1).
|
|
37
|
+
* @see lib/neatogen/adjust.c:simpleScale
|
|
38
|
+
*/
|
|
39
|
+
export function simpleScale(g: Graph): number {
|
|
40
|
+
const p = aggetGraph(g, 'scale');
|
|
41
|
+
if (p === undefined) return 0;
|
|
42
|
+
const sc = scanPair(p);
|
|
43
|
+
if (sc.length === 0) return 0;
|
|
44
|
+
const x = sc[0]!;
|
|
45
|
+
if (ISZERO(x)) return 0;
|
|
46
|
+
const y = sc.length === 1 ? x : sc[1]!;
|
|
47
|
+
if (ISZERO(y)) return 0;
|
|
48
|
+
if (y === 1 && x === 1) return 0;
|
|
49
|
+
for (const n of g.nodes.values()) {
|
|
50
|
+
n.info.pos![0] *= x;
|
|
51
|
+
n.info.pos![1] *= y;
|
|
52
|
+
}
|
|
53
|
+
return 1;
|
|
54
|
+
}
|
package/src/layout/sfdp/index.ts
CHANGED
|
@@ -31,7 +31,7 @@ import { CL_OFFSET } from '../twopi/pipeline.js';
|
|
|
31
31
|
import { placeGraphLabel } from '../dot/position-bbox.js';
|
|
32
32
|
import { gvPostprocess } from '../../common/postproc.js';
|
|
33
33
|
import { aggetGraph } from '../fdp/fdp-model.js';
|
|
34
|
-
import { overlapPrismTries, adjustNodesFull } from '../neato/fdp-adjust.js';
|
|
34
|
+
import { overlapPrismTries, adjustNodesFull, warnUnrecognizedOverlap } from '../neato/fdp-adjust.js';
|
|
35
35
|
import { lateDouble } from '../../common/nodeinit.js';
|
|
36
36
|
import {
|
|
37
37
|
type SpringElectricalControl,
|
|
@@ -98,6 +98,7 @@ function resolveControl(g: Graph): {
|
|
|
98
98
|
// graphAdjustMode(g, &am, "prism0"): agget NULL (unset) → "prism0" default.
|
|
99
99
|
const overlap = aggetGraph(g, 'overlap');
|
|
100
100
|
const flag = overlap === undefined ? 'prism0' : overlap;
|
|
101
|
+
warnUnrecognizedOverlap(flag); // graphAdjustMode → getAdjustMode, once per layout
|
|
101
102
|
const ntry = overlapPrismTries(flag);
|
|
102
103
|
if (ntry !== null) {
|
|
103
104
|
// AM_PRISM && doAdjust: overlap removal happens inside sfdp.
|
package/src/render/public.ts
CHANGED
|
@@ -60,13 +60,14 @@ export interface RenderOptions {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
// ---------------------------------------------------------------------------
|
|
63
|
-
//
|
|
63
|
+
// Argument validation (shared with the async entry points)
|
|
64
64
|
// ---------------------------------------------------------------------------
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Reject a bad `g` / `format` / `opts` before any work starts.
|
|
68
|
+
* @internal shared with src/async/render-async.ts; not part of the public API.
|
|
69
|
+
*/
|
|
70
|
+
export function checkRenderArgs(g: unknown, format: unknown, opts: unknown): void {
|
|
70
71
|
if (typeof g !== 'object' || g === null) {
|
|
71
72
|
throw invalidArgType('g', 'object', g);
|
|
72
73
|
}
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* VPSC-based overlap removal for the neato layout engine.
|
|
3
|
-
*
|
|
4
|
-
* Two independent VPSC passes (X then Y) ensure that no two node bounding
|
|
5
|
-
* boxes intersect after the call returns. Each pass builds its own Variables,
|
|
6
|
-
* Rectangles, and Constraints; none are shared between passes.
|
|
7
|
-
*
|
|
8
|
-
* @see lib/neatogen/adjust.c:vpscAdjust
|
|
9
|
-
* @see lib/vpsc/csolve_VPSC.h
|
|
10
|
-
*/
|
|
11
|
-
import type { Node } from '../../model/node.js';
|
|
12
|
-
import { type Variable, type Constraint, VPSC, Rectangle } from '../../vpsc/index.js';
|
|
13
|
-
/**
|
|
14
|
-
* Builds one Rectangle per node centred on pos[], expanding each half-size
|
|
15
|
-
* by sep/2 so the VPSC gap `(w_i+w_j)/2` incorporates the padding.
|
|
16
|
-
*
|
|
17
|
-
* @see lib/neatogen/adjust.c:vpscAdjust
|
|
18
|
-
*/
|
|
19
|
-
export declare function buildRectangles(nodes: Node[], sep: {
|
|
20
|
-
x: number;
|
|
21
|
-
y: number;
|
|
22
|
-
}): Rectangle[];
|
|
23
|
-
/**
|
|
24
|
-
* Allocates one Variable per node for a single VPSC axis pass.
|
|
25
|
-
*
|
|
26
|
-
* @see lib/vpsc/csolve_VPSC.h:newVariable
|
|
27
|
-
*/
|
|
28
|
-
export declare function allocateVariables(nodes: Node[], axis: 0 | 1): Variable[];
|
|
29
|
-
/**
|
|
30
|
-
* Writes solved positions back into node.info.pos[axis].
|
|
31
|
-
*
|
|
32
|
-
* @see lib/neatogen/adjust.c:vpscAdjust (position write-back loop)
|
|
33
|
-
*/
|
|
34
|
-
export declare function writePositions(nodes: Node[], vars: Variable[], axis: 0 | 1): void;
|
|
35
|
-
/**
|
|
36
|
-
* Tears down a VPSC pass in mandatory order (AD-4):
|
|
37
|
-
* deleteVPSC → deleteConstraints → deleteVariable per variable.
|
|
38
|
-
*
|
|
39
|
-
* @see lib/neatogen/adjust.c:vpscAdjust teardown sequence
|
|
40
|
-
*/
|
|
41
|
-
export declare function teardownPass(vpsc: VPSC, constraints: Constraint[], vars: Variable[]): void;
|
|
42
|
-
/**
|
|
43
|
-
* Runs one complete VPSC axis pass: solve constraints then write positions.
|
|
44
|
-
*
|
|
45
|
-
* @see lib/neatogen/adjust.c:vpscAdjust
|
|
46
|
-
*/
|
|
47
|
-
export declare function runAxisPass(nodes: Node[], sep: {
|
|
48
|
-
x: number;
|
|
49
|
-
y: number;
|
|
50
|
-
}, constraints: Constraint[], vars: Variable[], axis: 0 | 1): void;
|
|
51
|
-
/**
|
|
52
|
-
* One full VPSC pass on the X axis.
|
|
53
|
-
*
|
|
54
|
-
* @see lib/neatogen/adjust.c:vpscAdjust (X pass section)
|
|
55
|
-
*/
|
|
56
|
-
export declare function runXPass(nodes: Node[], sep: {
|
|
57
|
-
x: number;
|
|
58
|
-
y: number;
|
|
59
|
-
}): void;
|
|
60
|
-
/**
|
|
61
|
-
* One full VPSC pass on the Y axis.
|
|
62
|
-
*
|
|
63
|
-
* @see lib/neatogen/adjust.c:vpscAdjust (Y pass section)
|
|
64
|
-
*/
|
|
65
|
-
export declare function runYPass(nodes: Node[], sep: {
|
|
66
|
-
x: number;
|
|
67
|
-
y: number;
|
|
68
|
-
}): void;
|
|
69
|
-
/**
|
|
70
|
-
* Removes overlaps between nodes using two independent VPSC passes (X then Y).
|
|
71
|
-
*
|
|
72
|
-
* After this call no two node bounding boxes intersect (with sep padding).
|
|
73
|
-
* Positions are written into node.info.pos[0] (X) and node.info.pos[1] (Y).
|
|
74
|
-
*
|
|
75
|
-
* @param nodes - Nodes with info.pos, info.width, info.height set.
|
|
76
|
-
* @param sep - Minimum padding added beyond each node's half-size per axis.
|
|
77
|
-
*
|
|
78
|
-
* @see lib/neatogen/adjust.c:vpscAdjust
|
|
79
|
-
* @see lib/vpsc/csolve_VPSC.h
|
|
80
|
-
*/
|
|
81
|
-
export declare function removeOverlap(nodes: Node[], sep: {
|
|
82
|
-
x: number;
|
|
83
|
-
y: number;
|
|
84
|
-
}): void;
|
|
85
|
-
//# sourceMappingURL=overlap.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"overlap.d.ts","sourceRoot":"","sources":["../../../src/layout/neato/overlap.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAChD,OAAO,EAQL,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,IAAI,EACJ,SAAS,EACV,MAAM,qBAAqB,CAAC;AAE7B;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,IAAI,EAAE,EACb,GAAG,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5B,SAAS,EAAE,CAQb;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,GAAG,QAAQ,EAAE,CAExE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,IAAI,EAAE,EACb,IAAI,EAAE,QAAQ,EAAE,EAChB,IAAI,EAAE,CAAC,GAAG,CAAC,GACV,IAAI,CAON;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,IAAI,EACV,WAAW,EAAE,UAAU,EAAE,EACzB,IAAI,EAAE,QAAQ,EAAE,GACf,IAAI,CAIN;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,IAAI,EAAE,EACb,GAAG,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EAC7B,WAAW,EAAE,UAAU,EAAE,EACzB,IAAI,EAAE,QAAQ,EAAE,EAChB,IAAI,EAAE,CAAC,GAAG,CAAC,GACV,IAAI,CAKN;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CACtB,KAAK,EAAE,IAAI,EAAE,EACb,GAAG,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5B,IAAI,CAKN;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CACtB,KAAK,EAAE,IAAI,EAAE,EACb,GAAG,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5B,IAAI,CAKN;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,IAAI,EAAE,EACb,GAAG,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5B,IAAI,CAIN"}
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: EPL-2.0
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* VPSC-based overlap removal for the neato layout engine.
|
|
5
|
-
*
|
|
6
|
-
* Two independent VPSC passes (X then Y) ensure that no two node bounding
|
|
7
|
-
* boxes intersect after the call returns. Each pass builds its own Variables,
|
|
8
|
-
* Rectangles, and Constraints; none are shared between passes.
|
|
9
|
-
*
|
|
10
|
-
* @see lib/neatogen/adjust.c:vpscAdjust
|
|
11
|
-
* @see lib/vpsc/csolve_VPSC.h
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import type { Node } from '../../model/node.js';
|
|
15
|
-
import {
|
|
16
|
-
newVariable,
|
|
17
|
-
deleteVariable,
|
|
18
|
-
deleteVPSC,
|
|
19
|
-
deleteConstraints,
|
|
20
|
-
solveVPSC,
|
|
21
|
-
genXConstraints,
|
|
22
|
-
genYConstraints,
|
|
23
|
-
type Variable,
|
|
24
|
-
type Constraint,
|
|
25
|
-
VPSC,
|
|
26
|
-
Rectangle,
|
|
27
|
-
} from '../../vpsc/index.js';
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Builds one Rectangle per node centred on pos[], expanding each half-size
|
|
31
|
-
* by sep/2 so the VPSC gap `(w_i+w_j)/2` incorporates the padding.
|
|
32
|
-
*
|
|
33
|
-
* @see lib/neatogen/adjust.c:vpscAdjust
|
|
34
|
-
*/
|
|
35
|
-
export function buildRectangles(
|
|
36
|
-
nodes: Node[],
|
|
37
|
-
sep: { x: number; y: number },
|
|
38
|
-
): Rectangle[] {
|
|
39
|
-
return nodes.map((n) => {
|
|
40
|
-
const x = n.info.pos?.[0] ?? 0;
|
|
41
|
-
const y = n.info.pos?.[1] ?? 0;
|
|
42
|
-
const hw = n.info.width / 2 + sep.x / 2;
|
|
43
|
-
const hh = n.info.height / 2 + sep.y / 2;
|
|
44
|
-
return new Rectangle(x - hw, x + hw, y - hh, y + hh);
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Allocates one Variable per node for a single VPSC axis pass.
|
|
50
|
-
*
|
|
51
|
-
* @see lib/vpsc/csolve_VPSC.h:newVariable
|
|
52
|
-
*/
|
|
53
|
-
export function allocateVariables(nodes: Node[], axis: 0 | 1): Variable[] {
|
|
54
|
-
return nodes.map((n, i) => newVariable(i, n.info.pos?.[axis] ?? 0, 1));
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Writes solved positions back into node.info.pos[axis].
|
|
59
|
-
*
|
|
60
|
-
* @see lib/neatogen/adjust.c:vpscAdjust (position write-back loop)
|
|
61
|
-
*/
|
|
62
|
-
export function writePositions(
|
|
63
|
-
nodes: Node[],
|
|
64
|
-
vars: Variable[],
|
|
65
|
-
axis: 0 | 1,
|
|
66
|
-
): void {
|
|
67
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
68
|
-
const n = nodes[i]!;
|
|
69
|
-
if (!n.info.pos) n.info.pos = [0, 0];
|
|
70
|
-
n.info.pos[axis] =
|
|
71
|
-
vars[i]!.block !== null ? vars[i]!.position() : vars[i]!.desiredPosition;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Tears down a VPSC pass in mandatory order (AD-4):
|
|
77
|
-
* deleteVPSC → deleteConstraints → deleteVariable per variable.
|
|
78
|
-
*
|
|
79
|
-
* @see lib/neatogen/adjust.c:vpscAdjust teardown sequence
|
|
80
|
-
*/
|
|
81
|
-
export function teardownPass(
|
|
82
|
-
vpsc: VPSC,
|
|
83
|
-
constraints: Constraint[],
|
|
84
|
-
vars: Variable[],
|
|
85
|
-
): void {
|
|
86
|
-
deleteVPSC(vpsc);
|
|
87
|
-
deleteConstraints(constraints);
|
|
88
|
-
for (const v of vars) deleteVariable(v);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Runs one complete VPSC axis pass: solve constraints then write positions.
|
|
93
|
-
*
|
|
94
|
-
* @see lib/neatogen/adjust.c:vpscAdjust
|
|
95
|
-
*/
|
|
96
|
-
export function runAxisPass(
|
|
97
|
-
nodes: Node[],
|
|
98
|
-
sep: { x: number; y: number },
|
|
99
|
-
constraints: Constraint[],
|
|
100
|
-
vars: Variable[],
|
|
101
|
-
axis: 0 | 1,
|
|
102
|
-
): void {
|
|
103
|
-
const vpsc = new VPSC(vars, constraints);
|
|
104
|
-
solveVPSC(vpsc);
|
|
105
|
-
writePositions(nodes, vars, axis);
|
|
106
|
-
teardownPass(vpsc, constraints, vars);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* One full VPSC pass on the X axis.
|
|
111
|
-
*
|
|
112
|
-
* @see lib/neatogen/adjust.c:vpscAdjust (X pass section)
|
|
113
|
-
*/
|
|
114
|
-
export function runXPass(
|
|
115
|
-
nodes: Node[],
|
|
116
|
-
sep: { x: number; y: number },
|
|
117
|
-
): void {
|
|
118
|
-
const rects = buildRectangles(nodes, sep);
|
|
119
|
-
const vars = allocateVariables(nodes, 0);
|
|
120
|
-
const cs = genXConstraints(rects, vars, true);
|
|
121
|
-
runAxisPass(nodes, sep, cs, vars, 0);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* One full VPSC pass on the Y axis.
|
|
126
|
-
*
|
|
127
|
-
* @see lib/neatogen/adjust.c:vpscAdjust (Y pass section)
|
|
128
|
-
*/
|
|
129
|
-
export function runYPass(
|
|
130
|
-
nodes: Node[],
|
|
131
|
-
sep: { x: number; y: number },
|
|
132
|
-
): void {
|
|
133
|
-
const rects = buildRectangles(nodes, sep);
|
|
134
|
-
const vars = allocateVariables(nodes, 1);
|
|
135
|
-
const cs = genYConstraints(rects, vars);
|
|
136
|
-
runAxisPass(nodes, sep, cs, vars, 1);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Removes overlaps between nodes using two independent VPSC passes (X then Y).
|
|
141
|
-
*
|
|
142
|
-
* After this call no two node bounding boxes intersect (with sep padding).
|
|
143
|
-
* Positions are written into node.info.pos[0] (X) and node.info.pos[1] (Y).
|
|
144
|
-
*
|
|
145
|
-
* @param nodes - Nodes with info.pos, info.width, info.height set.
|
|
146
|
-
* @param sep - Minimum padding added beyond each node's half-size per axis.
|
|
147
|
-
*
|
|
148
|
-
* @see lib/neatogen/adjust.c:vpscAdjust
|
|
149
|
-
* @see lib/vpsc/csolve_VPSC.h
|
|
150
|
-
*/
|
|
151
|
-
export function removeOverlap(
|
|
152
|
-
nodes: Node[],
|
|
153
|
-
sep: { x: number; y: number },
|
|
154
|
-
): void {
|
|
155
|
-
if (nodes.length < 2) return;
|
|
156
|
-
runXPass(nodes, sep);
|
|
157
|
-
runYPass(nodes, sep);
|
|
158
|
-
}
|