@knowvah/dot-engine 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/async/collect.d.ts.map +1 -1
  2. package/dist/async/render-async.d.ts.map +1 -1
  3. package/dist/common/arith.d.ts +6 -0
  4. package/dist/common/arith.d.ts.map +1 -1
  5. package/dist/common/poly-sizing.d.ts.map +1 -1
  6. package/dist/common/textmeasure-factory.d.ts +2 -1
  7. package/dist/common/textmeasure-factory.d.ts.map +1 -1
  8. package/dist/common/textmeasure.d.ts +1 -1
  9. package/dist/common/textmeasure.d.ts.map +1 -1
  10. package/dist/gvc/device-labels.d.ts +30 -0
  11. package/dist/gvc/device-labels.d.ts.map +1 -0
  12. package/dist/gvc/device-transform.d.ts +22 -0
  13. package/dist/gvc/device-transform.d.ts.map +1 -0
  14. package/dist/gvc/device.d.ts +2 -42
  15. package/dist/gvc/device.d.ts.map +1 -1
  16. package/dist/index.js +98 -79
  17. package/dist/index.js.map +4 -4
  18. package/dist/layout/dot/graph-label.d.ts +10 -0
  19. package/dist/layout/dot/graph-label.d.ts.map +1 -1
  20. package/dist/layout/dot/splines-label.d.ts.map +1 -1
  21. package/dist/layout/fdp/normalize.d.ts.map +1 -1
  22. package/dist/layout/fdp/xlayout.d.ts +1 -1
  23. package/dist/layout/neato/dijkstra.d.ts +0 -32
  24. package/dist/layout/neato/dijkstra.d.ts.map +1 -1
  25. package/dist/layout/neato/fdp-adjust.d.ts +12 -1
  26. package/dist/layout/neato/fdp-adjust.d.ts.map +1 -1
  27. package/dist/layout/neato/index.d.ts +5 -5
  28. package/dist/layout/neato/index.d.ts.map +1 -1
  29. package/dist/layout/sfdp/index.d.ts.map +1 -1
  30. package/dist/render/public.d.ts +5 -0
  31. package/dist/render/public.d.ts.map +1 -1
  32. package/dist/render.js +95 -76
  33. package/dist/render.js.map +4 -4
  34. package/package.json +1 -1
  35. package/src/async/collect.ts +2 -14
  36. package/src/async/render-async.ts +7 -12
  37. package/src/common/arith.ts +9 -0
  38. package/src/common/poly-sizing.ts +2 -1
  39. package/src/common/textmeasure-factory.ts +20 -1
  40. package/src/common/textmeasure.ts +1 -1
  41. package/src/gvc/device-labels.ts +98 -0
  42. package/src/gvc/device-transform.ts +58 -0
  43. package/src/gvc/device.ts +8 -132
  44. package/src/layout/dot/graph-label.ts +6 -1
  45. package/src/layout/dot/splines-label.ts +2 -1
  46. package/src/layout/fdp/normalize.ts +2 -1
  47. package/src/layout/fdp/xlayout.ts +3 -2
  48. package/src/layout/neato/dijkstra.ts +0 -141
  49. package/src/layout/neato/fdp-adjust.ts +25 -3
  50. package/src/layout/neato/index.ts +13 -12
  51. package/src/layout/sfdp/index.ts +2 -1
  52. package/src/render/public.ts +6 -5
  53. package/dist/layout/neato/overlap.d.ts +0 -85
  54. package/dist/layout/neato/overlap.d.ts.map +0 -1
  55. 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
- }
@@ -151,12 +151,26 @@ const ADJUST_MODES = new Map<string, readonly [number, string]>([
151
151
  ['porthoyx', [AM_PORTHOYX, 'yx pseudo-orthogonal constraints']],
152
152
  ]);
153
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
+
154
167
  /**
155
168
  * Resolve an `overlap` value to its adjust mode (the PRISM try count lands in
156
- * `value`).
169
+ * `value`), warning as C does for an unrecognized value.
157
170
  * @see lib/neatogen/adjust.c:getAdjustMode
158
171
  */
159
172
  export function getAdjustMode(flag: string | undefined): AdjustData {
173
+ warnUnrecognizedOverlap(flag);
160
174
  const ntry = overlapPrismTries(flag);
161
175
  if (ntry !== null) return { mode: AM_PRISM, print: 'prism', value: ntry };
162
176
  const named = ADJUST_MODES.get((flag ?? '').toLowerCase());
@@ -227,6 +241,14 @@ export function removeOverlapWith(g: Graph, am: AdjustData): number {
227
241
  * @see lib/neatogen/adjust.c:adjustNodes / removeOverlapAs
228
242
  */
229
243
  export function adjustNodesFull(g: Graph): number {
230
- const flag = g.attrs.get('overlap') ?? g.root.attrs.get('overlap');
231
- return removeOverlapWith(g, getAdjustMode(flag));
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'));
232
254
  }
@@ -25,7 +25,7 @@ import {
25
25
  MODEL_MDS,
26
26
  } from './init.js';
27
27
  import { neatoMode } from './start.js';
28
- import { adjustNodesFull } from './fdp-adjust.js';
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 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.
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 (adjustNodes call)
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
- adjustNodesFull(g);
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, model: number, pinned: boolean,
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
@@ -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.
@@ -60,13 +60,14 @@ export interface RenderOptions {
60
60
  }
61
61
 
62
62
  // ---------------------------------------------------------------------------
63
- // Private helpers (mirrors index.ts)
63
+ // Argument validation (shared with the async entry points)
64
64
  // ---------------------------------------------------------------------------
65
65
 
66
-
67
-
68
- /** Reject a bad `g` / `format` / `opts` before any work starts. */
69
- function checkRenderArgs(g: unknown, format: unknown, opts: unknown): void {
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
- }