@knowvah/dot-engine 2.6.2 → 2.6.3
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/common/htmltable-lex.d.ts +38 -0
- package/dist/common/htmltable-lex.d.ts.map +1 -1
- package/dist/common/htmltable-parse-attrs.d.ts +41 -0
- package/dist/common/htmltable-parse-attrs.d.ts.map +1 -0
- package/dist/common/htmltable-parse.d.ts +3 -0
- package/dist/common/htmltable-parse.d.ts.map +1 -1
- package/dist/common/htmltable-pos.d.ts +17 -0
- package/dist/common/htmltable-pos.d.ts.map +1 -1
- package/dist/common/htmltable.d.ts +13 -1
- package/dist/common/htmltable.d.ts.map +1 -1
- package/dist/common/make-label.d.ts.map +1 -1
- package/dist/common/spline-midpoint.d.ts +8 -0
- package/dist/common/spline-midpoint.d.ts.map +1 -1
- package/dist/index.js +617 -322
- package/dist/index.js.map +4 -4
- package/dist/label/node.d.ts +7 -1
- package/dist/label/node.d.ts.map +1 -1
- package/dist/layout/circo/position-helpers.d.ts +15 -3
- package/dist/layout/circo/position-helpers.d.ts.map +1 -1
- package/dist/layout/circo/position.d.ts +17 -1
- package/dist/layout/circo/position.d.ts.map +1 -1
- package/dist/layout/dot/ortho-adapter.d.ts.map +1 -1
- package/dist/layout/neato/multispline.d.ts.map +1 -1
- package/dist/layout/pack/poly-pack.d.ts +2 -18
- package/dist/layout/pack/poly-pack.d.ts.map +1 -1
- package/dist/pathplan/shortest.d.ts.map +1 -1
- package/dist/render.js +770 -475
- package/dist/render.js.map +4 -4
- package/package.json +1 -1
- package/src/common/htmltable-lex.ts +265 -5
- package/src/common/htmltable-parse-attrs.ts +166 -0
- package/src/common/htmltable-parse.ts +262 -242
- package/src/common/htmltable-pos.ts +37 -38
- package/src/common/htmltable.ts +139 -45
- package/src/common/make-label.ts +19 -3
- package/src/common/spline-midpoint.ts +30 -9
- package/src/label/node.ts +8 -2
- package/src/layout/circo/position-helpers.ts +28 -17
- package/src/layout/circo/position.ts +28 -7
- package/src/layout/dot/ortho-adapter.ts +20 -9
- package/src/layout/neato/multispline.ts +17 -5
- package/src/layout/pack/poly-pack.ts +5 -31
- package/src/pathplan/shortest.ts +15 -2
|
@@ -15,6 +15,7 @@ import type { Block, SubGraph, CircState } from './blocks.js';
|
|
|
15
15
|
import { blkParent, setCoalesced, FLAGS_ISPARENT } from './blocks.js';
|
|
16
16
|
import { applyDelta, getRotation } from './position-helpers.js';
|
|
17
17
|
import { layoutBlock } from './blockpath.js';
|
|
18
|
+
import { fma } from '../../common/fma.js';
|
|
18
19
|
|
|
19
20
|
export type PosState = {
|
|
20
21
|
radius: number; subtreeR: number; nodeAngle: number;
|
|
@@ -48,10 +49,14 @@ export function getInfo(pi: PosInfo, stp: PosState, minDist: number): number {
|
|
|
48
49
|
return maxR;
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* The numerator is one clang-contracted fmadd: p0.diameter * p1.minRadius
|
|
54
|
+
* added to the rounded p1.diameter * p0.minRadius (objdump of `_doBlock`).
|
|
55
|
+
* @see lib/circogen/circpos.c:setInfo
|
|
56
|
+
*/
|
|
52
57
|
export function setInfo(p0: PosInfo, p1: PosInfo, delta: number): void {
|
|
53
58
|
const t = Math.max(
|
|
54
|
-
(p0.diameter
|
|
59
|
+
fma(p0.diameter, p1.minRadius, p1.diameter * p0.minRadius)
|
|
55
60
|
/ (2 * delta * p0.minRadius * p1.minRadius), 1,
|
|
56
61
|
);
|
|
57
62
|
p0.scale = Math.max(p0.scale, t);
|
|
@@ -63,9 +68,25 @@ export function placeChild(child: Block, ca: number, ctx: PlaceCtx): number {
|
|
|
63
68
|
const dx = ctx.childRadius * Math.cos(ca);
|
|
64
69
|
const dy = ctx.childRadius * Math.sin(ca);
|
|
65
70
|
applyDelta(child, dx, dy, getRotation(child, dx, dy, ca));
|
|
66
|
-
return ctx.
|
|
67
|
-
|
|
68
|
-
|
|
71
|
+
return nextChildAngle(ca, ctx.incAngle, ctx.mindistAngle, ctx.length);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The angle after placing a child. C's `childAngle += incidentAngle +
|
|
76
|
+
* mindistAngle` (or `+ mindistAngle / 2`) adds the right-hand sum last.
|
|
77
|
+
* @see lib/circogen/circpos.c:positionChildren
|
|
78
|
+
*/
|
|
79
|
+
export function nextChildAngle(ca: number, inc: number, mda: number, length: number): number {
|
|
80
|
+
return length === 1 ? ca + (inc + mda) : ca + (inc + mda / 2);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Circumference left over on a length-1 block's child circle: C's
|
|
85
|
+
* `2 * M_PI * childRadius - info->diameter`, contracted to one fnmsub.
|
|
86
|
+
* @see lib/circogen/circpos.c:positionChildren
|
|
87
|
+
*/
|
|
88
|
+
export function leftoverArc(childRadius: number, diameter: number): number {
|
|
89
|
+
return fma(2 * Math.PI, childRadius, -diameter);
|
|
69
90
|
}
|
|
70
91
|
|
|
71
92
|
/** Advance child angle for length===1 blocks; updates firstAngle/lastAngle. */
|
|
@@ -81,7 +102,7 @@ export function advanceAngleL1(
|
|
|
81
102
|
/** Advance child angle for multi-node blocks. */
|
|
82
103
|
export function advanceAngleLN(ca: number, inc: number, mda: number, total: number, theta: number): number {
|
|
83
104
|
if (total === 1) return theta;
|
|
84
|
-
return ca + inc + mda / 2;
|
|
105
|
+
return ca + (inc + mda / 2); // C: childAngle += incidentAngle + mindistAngle / 2
|
|
85
106
|
}
|
|
86
107
|
|
|
87
108
|
/** Write psi midpoint angle to node alg when conditions are met. */
|
|
@@ -101,7 +122,7 @@ export function positionChildren(pi: PosInfo, stp: PosState, length: number, min
|
|
|
101
122
|
if (length === 1) {
|
|
102
123
|
cr = Math.max(cr, pi.diameter / (2 * Math.PI));
|
|
103
124
|
// C redistributes leftover circumference into min_dist.
|
|
104
|
-
const d =
|
|
125
|
+
const d = leftoverArc(cr, pi.diameter);
|
|
105
126
|
if (d > 0) md += d / pi.childCount;
|
|
106
127
|
}
|
|
107
128
|
stp.subtreeR = Math.max(stp.subtreeR, cr + pi.maxRadius);
|
|
@@ -16,6 +16,7 @@ import type { Edge } from '../../model/edge.js';
|
|
|
16
16
|
import { buildOutEdgeIndex } from '../../model/node.js';
|
|
17
17
|
import { dotRoot } from './mincross-utils.js';
|
|
18
18
|
import { nodesInSeq } from './decomp.js';
|
|
19
|
+
import { PointSet } from '../../common/pointset.js';
|
|
19
20
|
import { clipAndInstall } from '../../common/splines-clip.js';
|
|
20
21
|
import { buildDotSinfo } from './self-loop.js';
|
|
21
22
|
import { orthoEdges } from '../../ortho/index.js';
|
|
@@ -49,11 +50,27 @@ function buildNodes(nodeArr: Node[]): OrthoNodeArr {
|
|
|
49
50
|
}));
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Concentrate dedup: true when the unordered (tail, head) pair is already in
|
|
55
|
+
* `ps`; otherwise records it. C normalises AGSEQ(tail)/AGSEQ(head) to
|
|
56
|
+
* (min, max) and makes one isInPS/addPS pair on it.
|
|
57
|
+
* @see lib/ortho/ortho.c:1221-1229 (orthoEdges Concentrate PS)
|
|
58
|
+
*/
|
|
59
|
+
function isConcentrateDup(ps: PointSet, e: Edge): boolean {
|
|
60
|
+
const ti = e.tail.id;
|
|
61
|
+
const hi = e.head.id;
|
|
62
|
+
const lo = ti <= hi ? ti : hi;
|
|
63
|
+
const up = ti <= hi ? hi : ti;
|
|
64
|
+
if (ps.isInPS(lo, up)) return true;
|
|
65
|
+
ps.addPS(lo, up);
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
52
69
|
/**
|
|
53
70
|
* Gather edges to route, mirroring C's ortho gather loop exactly:
|
|
54
71
|
* iterate nodes in agfstnode (creation/seq) order, then each node's out-edges
|
|
55
72
|
* in agfstout order. When `concentrate` is set, dedup by the UNORDERED node
|
|
56
|
-
* pair via a
|
|
73
|
+
* pair via a `PointSet` — the first edge between a pair (in either direction)
|
|
57
74
|
* is routed; any later edge sharing that pair (the reverse of a 2-cycle, or a
|
|
58
75
|
* parallel multi-edge) is skipped. This is ortho's own concentrate mechanism,
|
|
59
76
|
* distinct from class2's `edge_type == IGNORED` marking, and the keep-first
|
|
@@ -70,19 +87,13 @@ function buildEdges(
|
|
|
70
87
|
const nodeIndex = new Map<Node, number>();
|
|
71
88
|
nodeArr.forEach((n, i) => nodeIndex.set(n, i));
|
|
72
89
|
const outIndex = buildOutEdgeIndex(g);
|
|
73
|
-
const
|
|
90
|
+
const ps = concentrate ? new PointSet() : null;
|
|
74
91
|
const result: TaggedOrthoEdge[] = [];
|
|
75
92
|
for (const n of nodeArr) {
|
|
76
93
|
const oes = outIndex.get(n);
|
|
77
94
|
if (oes === undefined) continue;
|
|
78
95
|
for (const e of oes) {
|
|
79
|
-
if (
|
|
80
|
-
const ti = e.tail.id;
|
|
81
|
-
const hi = e.head.id;
|
|
82
|
-
const key = ti <= hi ? `${ti},${hi}` : `${hi},${ti}`;
|
|
83
|
-
if (seen.has(key)) continue;
|
|
84
|
-
seen.add(key);
|
|
85
|
-
}
|
|
96
|
+
if (ps !== null && isConcentrateDup(ps, e)) continue;
|
|
86
97
|
result.push({
|
|
87
98
|
tail: orthoNodes[nodeIndex.get(e.tail)!],
|
|
88
99
|
head: orthoNodes[nodeIndex.get(e.head)!],
|
|
@@ -333,6 +333,18 @@ function chainOf(e: Edge): Edge[] {
|
|
|
333
333
|
return list;
|
|
334
334
|
}
|
|
335
335
|
|
|
336
|
+
/**
|
|
337
|
+
* agwarningf at each genroute failure site (Graphviz prefixes "Warning: ");
|
|
338
|
+
* returns genroute's failure code 1.
|
|
339
|
+
* @see multispline.c:genroute
|
|
340
|
+
*/
|
|
341
|
+
function warnNoControlPoints(e: Edge): number {
|
|
342
|
+
console.warn(
|
|
343
|
+
`Warning: Could not create control points for multiple spline for edge (${e.tail.name},${e.head.name})`,
|
|
344
|
+
);
|
|
345
|
+
return 1;
|
|
346
|
+
}
|
|
347
|
+
|
|
336
348
|
/**
|
|
337
349
|
* Generate splines for e and cohorts through the corridor polygon.
|
|
338
350
|
* Edges run from polygon index 0 to t. Returns 0 on success.
|
|
@@ -350,7 +362,7 @@ function genroute(
|
|
|
350
362
|
const head = e.head;
|
|
351
363
|
|
|
352
364
|
const pl = shortestPath({ ps: trip.ps }, eps);
|
|
353
|
-
if (pl === null) return
|
|
365
|
+
if (pl === null) return warnNoControlPoints(e);
|
|
354
366
|
|
|
355
367
|
if (pl.length === 2) {
|
|
356
368
|
// C passes doPolyline (0/1) AS the edge type — so the polyline case
|
|
@@ -365,7 +377,7 @@ function genroute(
|
|
|
365
377
|
|
|
366
378
|
if (mult === 1 || concentrate) {
|
|
367
379
|
const spl = routeMember(trip.ps, t, pl, doPolyline);
|
|
368
|
-
if (spl === null) return
|
|
380
|
+
if (spl === null) return warnNoControlPoints(e);
|
|
369
381
|
finishEdge(e, spl, e.head !== head);
|
|
370
382
|
return 0;
|
|
371
383
|
}
|
|
@@ -374,7 +386,7 @@ function genroute(
|
|
|
374
386
|
const cpts: Point[][] = [];
|
|
375
387
|
for (let i = 0; i + 2 < pl.length; i++) {
|
|
376
388
|
const cp = mkCtrlPts(t, mult + 1, pl[i]!, pl[i + 1]!, pl[i + 2]!, trip);
|
|
377
|
-
if (cp === null) return
|
|
389
|
+
if (cp === null) return warnNoControlPoints(e);
|
|
378
390
|
cpts.push(cp);
|
|
379
391
|
}
|
|
380
392
|
|
|
@@ -390,10 +402,10 @@ function genroute(
|
|
|
390
402
|
poly[pn - j] = cpts[j - 1]![i + 1]!;
|
|
391
403
|
}
|
|
392
404
|
const mmpl = shortestPath({ ps: poly }, eps);
|
|
393
|
-
if (mmpl === null) return
|
|
405
|
+
if (mmpl === null) return warnNoControlPoints(cur);
|
|
394
406
|
|
|
395
407
|
const spl = routeMember(poly, pl.length - 1, mmpl, doPolyline);
|
|
396
|
-
if (spl === null) return
|
|
408
|
+
if (spl === null) return warnNoControlPoints(cur);
|
|
397
409
|
finishEdge(cur, spl, cur.head !== head);
|
|
398
410
|
|
|
399
411
|
cur = cur.info.to_virt;
|
|
@@ -17,6 +17,7 @@ import type { PackInfo } from './types.js';
|
|
|
17
17
|
import { spiralSearch } from './spiral-search.js';
|
|
18
18
|
import { gvQsort } from '../../util/bsd-qsort.js';
|
|
19
19
|
import { cround } from '../../common/arith.js';
|
|
20
|
+
import { PointSet } from '../../common/pointset.js';
|
|
20
21
|
import { d2i } from '../../util/math.js';
|
|
21
22
|
|
|
22
23
|
/** Max average polyomino size constant. @see lib/pack/pack.c:C */
|
|
@@ -29,33 +30,6 @@ export interface GInfo {
|
|
|
29
30
|
index: number;
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
/** A set of occupied grid cells (string-keyed for O(1) lookup). */
|
|
33
|
-
export type PointSet = Set<string>;
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Encode a grid point as a canonical string key.
|
|
37
|
-
*
|
|
38
|
-
* C keys its PointSet dict on the `pointf` cell itself (exact double compare,
|
|
39
|
-
* no rounding). Every cell coordinate reaching here is an exact integer grid
|
|
40
|
-
* index produced by genBox's `cround(cval(...))`, so the rounding mode is
|
|
41
|
-
* unobservable; `cround` is used only so no `Math.round` survives in a layout
|
|
42
|
-
* coordinate path.
|
|
43
|
-
* @see lib/common/pointset.h:45 (insertPS/inPS take pointf)
|
|
44
|
-
*/
|
|
45
|
-
export function psKey(x: number, y: number): string {
|
|
46
|
-
return `${cround(x)},${cround(y)}`;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** Check if a cell is in the point set. @see lib/pack/pack.c:inPS */
|
|
50
|
-
export function inPS(ps: PointSet, p: Point): boolean {
|
|
51
|
-
return ps.has(psKey(p.x, p.y));
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** Insert a cell into the point set. @see lib/pack/pack.c:insertPS */
|
|
55
|
-
export function insertPS(ps: PointSet, p: Point): void {
|
|
56
|
-
ps.add(psKey(p.x, p.y));
|
|
57
|
-
}
|
|
58
|
-
|
|
59
33
|
/**
|
|
60
34
|
* Compute the polyomino grid step size (positive root of quadratic).
|
|
61
35
|
*
|
|
@@ -167,7 +141,7 @@ export interface PlaceCtx {
|
|
|
167
141
|
export function hasCollision(cells: Point[], dx: number, dy: number, ps: PointSet): boolean {
|
|
168
142
|
for (let i = 0; i < cells.length; i++) {
|
|
169
143
|
const cell = cells[i];
|
|
170
|
-
if (cell !== undefined && inPS(
|
|
144
|
+
if (cell !== undefined && ps.inPS({ x: cell.x + dx, y: cell.y + dy })) return true;
|
|
171
145
|
}
|
|
172
146
|
return false;
|
|
173
147
|
}
|
|
@@ -175,12 +149,12 @@ export function hasCollision(cells: Point[], dx: number, dy: number, ps: PointSe
|
|
|
175
149
|
/**
|
|
176
150
|
* Mark all shifted cells as occupied in the point set.
|
|
177
151
|
*
|
|
178
|
-
* @see lib/pack/pack.c:fits (cell insertion loop)
|
|
152
|
+
* @see lib/pack/pack.c:fits (cell insertion loop; `addPS`)
|
|
179
153
|
*/
|
|
180
154
|
export function markCells(cells: Point[], dx: number, dy: number, ps: PointSet): void {
|
|
181
155
|
for (let i = 0; i < cells.length; i++) {
|
|
182
156
|
const cell = cells[i];
|
|
183
|
-
if (cell !== undefined)
|
|
157
|
+
if (cell !== undefined) ps.addPS(cell.x + dx, cell.y + dy);
|
|
184
158
|
}
|
|
185
159
|
}
|
|
186
160
|
|
|
@@ -268,7 +242,7 @@ export function polyRects(ng: number, bbs: Box[], pinfo: PackInfo): Point[] | nu
|
|
|
268
242
|
// perimeter, so the tie order of equal-perimeter components is decided by libc
|
|
269
243
|
// qsort, not insertion order. Match it. @see util/bsd-qsort.ts
|
|
270
244
|
const sorted = gvQsort([...infos], cmpByGPerimeter);
|
|
271
|
-
const ctx: PlaceCtx = { ps: new
|
|
245
|
+
const ctx: PlaceCtx = { ps: new PointSet(), step, bbs };
|
|
272
246
|
const places: Point[] = new Array<Point>(ng).fill({ x: 0, y: 0 });
|
|
273
247
|
for (let i = 0; i < sorted.length; i++) {
|
|
274
248
|
const info = sorted[i];
|
package/src/pathplan/shortest.ts
CHANGED
|
@@ -250,9 +250,22 @@ class ShortestHelper {
|
|
|
250
250
|
ShortestHelper.connectAll(tris);
|
|
251
251
|
const ftrii = ShortestHelper.findTri(tris, eps[0]);
|
|
252
252
|
const ltrii = ShortestHelper.findTri(tris, eps[1]);
|
|
253
|
-
|
|
253
|
+
// C finds the first triangle, fails if none, and only then looks for the
|
|
254
|
+
// last one (shortest.c:178-204), so the two messages are exclusive.
|
|
255
|
+
if (ftrii === NO_TRI) {
|
|
256
|
+
console.warn('source point not in any triangle');
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
if (ltrii === NO_TRI) {
|
|
260
|
+
console.warn('destination point not in any triangle');
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
254
263
|
if (ftrii === ltrii) return [eps[0], eps[1]];
|
|
255
|
-
if (!ShortestHelper.markPath(tris, ftrii, ltrii))
|
|
264
|
+
if (!ShortestHelper.markPath(tris, ftrii, ltrii)) {
|
|
265
|
+
console.warn('cannot find triangle path');
|
|
266
|
+
// C: a straight line is better than failing
|
|
267
|
+
return [eps[0], eps[1]];
|
|
268
|
+
}
|
|
256
269
|
const epnls: [PNL, PNL] = [{ pp: eps[0], link: null }, { pp: eps[1], link: null }];
|
|
257
270
|
const dq = ShortestHelper.initDeque(pnlps.length);
|
|
258
271
|
ShortestHelper.add2dq(dq, DQ_FRONT, epnls[0]);
|