@knowvah/dot-engine 1.5.0 → 1.6.1
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/api/geometry.d.ts +21 -3
- package/dist/api/geometry.d.ts.map +1 -1
- package/dist/api.js +4 -4
- package/dist/api.js.map +2 -2
- package/dist/cdt/splay.d.ts.map +1 -1
- package/dist/common/htmltable-emit-rules.d.ts.map +1 -1
- package/dist/common/shape-clip.d.ts +17 -0
- package/dist/common/shape-clip.d.ts.map +1 -0
- package/dist/index.js +601 -578
- package/dist/index.js.map +4 -4
- package/dist/layout/dot/edge-route.d.ts.map +1 -1
- package/dist/layout/dot/pack-components.d.ts.map +1 -1
- package/dist/layout/dot/sameport.d.ts +1 -13
- package/dist/layout/dot/sameport.d.ts.map +1 -1
- package/dist/layout/dot/self-loop.d.ts.map +1 -1
- package/dist/layout/dot/splines-groups.d.ts.map +1 -1
- package/dist/layout/neato/index.d.ts.map +1 -1
- package/dist/layout/neato/init.d.ts.map +1 -1
- package/dist/ortho/ortho-route.d.ts.map +1 -1
- package/dist/render/dot.d.ts.map +1 -1
- package/dist/render.js +1209 -1186
- package/dist/render.js.map +4 -4
- package/package.json +14 -5
- package/src/api/geometry.ts +25 -10
- package/src/cdt/splay.ts +0 -2
- package/src/common/arm-pow.ts +3 -3
- package/src/common/htmltable-emit-rules.ts +0 -10
- package/src/common/random.ts +2 -2
- package/src/common/shape-clip.ts +48 -0
- package/src/common/spline-midpoint.ts +1 -1
- package/src/layout/circo/circular.ts +1 -1
- package/src/layout/dot/edge-route.ts +18 -6
- package/src/layout/dot/ns.ts +1 -1
- package/src/layout/dot/pack-components.ts +0 -1
- package/src/layout/dot/sameport.ts +1 -25
- package/src/layout/dot/self-loop.ts +4 -2
- package/src/layout/dot/splines-flat-labeled.ts +1 -1
- package/src/layout/dot/splines-groups.ts +8 -1
- package/src/layout/neato/cdt-surface.ts +1 -1
- package/src/layout/neato/index.ts +0 -3
- package/src/layout/neato/init.ts +0 -1
- package/src/ortho/ortho-route.ts +1 -3
- package/src/ortho/trap-query.ts +1 -1
- package/src/render/dot.ts +3 -10
- package/src/vpsc/Solver.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knowvah/dot-engine",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"description": "Faithful TypeScript port of Graphviz (dot, neato, fdp, sfdp, circo, twopi, osage, patchwork) — no C: no native binary, no WASM. Oracle-verified against the C implementation, runs in the browser.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"graphviz",
|
|
@@ -51,12 +51,17 @@
|
|
|
51
51
|
}
|
|
52
52
|
},
|
|
53
53
|
"scripts": {
|
|
54
|
+
"prebuild": "npm run parser:regen",
|
|
54
55
|
"build": "npm run build:js && npm run build:types",
|
|
55
56
|
"build:js": "esbuild src/index.ts --bundle --format=esm --sourcemap --outfile=dist/index.js && esbuild src/api/index.ts --bundle --format=esm --sourcemap --outfile=dist/api.js && esbuild src/render/index.ts --bundle --format=esm --sourcemap --outfile=dist/render.js",
|
|
56
57
|
"build:types": "tsc -p tsconfig.build.json",
|
|
58
|
+
"pretest": "npm run parser:regen",
|
|
57
59
|
"test": "vitest run",
|
|
60
|
+
"precoverage": "npm run parser:regen",
|
|
58
61
|
"coverage": "vitest run --coverage",
|
|
62
|
+
"pretypecheck": "npm run parser:regen",
|
|
59
63
|
"typecheck": "tsc --noEmit --stableTypeOrdering",
|
|
64
|
+
"pretypecheck:ts7": "npm run parser:regen",
|
|
60
65
|
"typecheck:ts7": "tsgo --noEmit",
|
|
61
66
|
"parser:regen": "peggy --format es --dts src/parser/dot.pegjs -o src/parser/dot.js",
|
|
62
67
|
"survey:setup": "sh test/corpus/gen-headless-gvbindir.sh",
|
|
@@ -70,6 +75,7 @@
|
|
|
70
75
|
"docs:copy-goldens": "node docs-site/copy-goldens.mjs",
|
|
71
76
|
"docs:api": "typedoc",
|
|
72
77
|
"docs:dev": "npm run docs:copy-reports && npm run docs:copy-goldens && npm run docs:api && vitepress dev docs-site",
|
|
78
|
+
"predocs:build": "npm run parser:regen",
|
|
73
79
|
"docs:build": "npm run docs:copy-reports && npm run docs:copy-goldens && npm run docs:api && vitepress build docs-site",
|
|
74
80
|
"docs:preview": "vitepress preview docs-site",
|
|
75
81
|
"release": "semantic-release",
|
|
@@ -96,8 +102,8 @@
|
|
|
96
102
|
"@types/fontkit": "^2.0.9",
|
|
97
103
|
"@types/node": "^22.0.0",
|
|
98
104
|
"@typescript/native-preview": "^7.0.0-dev.20260622.1",
|
|
99
|
-
"@vitest/coverage-v8": "^4.1.
|
|
100
|
-
"@xmldom/xmldom": "^0.9.
|
|
105
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
106
|
+
"@xmldom/xmldom": "^0.9.12",
|
|
101
107
|
"esbuild": "^0.28.1",
|
|
102
108
|
"fontkit": "^2.0.4",
|
|
103
109
|
"peggy": "^5.0.0",
|
|
@@ -109,11 +115,14 @@
|
|
|
109
115
|
"typedoc-vitepress-theme": "^1.1.3",
|
|
110
116
|
"typescript": "^6.0.0",
|
|
111
117
|
"vitepress": "^1.6.0",
|
|
112
|
-
"vitest": "^4.1.
|
|
118
|
+
"vitest": "^4.1.11"
|
|
113
119
|
},
|
|
114
120
|
"overrides": {
|
|
115
121
|
"postcss": "^8.5.25",
|
|
116
|
-
"brace-expansion": "^5.0.9"
|
|
122
|
+
"brace-expansion": "^5.0.9",
|
|
123
|
+
"vitepress": {
|
|
124
|
+
"vite": "^6.4.3"
|
|
125
|
+
}
|
|
117
126
|
},
|
|
118
127
|
"volta": {
|
|
119
128
|
"node": "26.3.1"
|
package/src/api/geometry.ts
CHANGED
|
@@ -97,7 +97,10 @@ export interface NodeGeometry {
|
|
|
97
97
|
*
|
|
98
98
|
* `points` concatenates all bezier control points from the edge spline,
|
|
99
99
|
* in order. An edge with no routed spline produces an empty `points` array.
|
|
100
|
-
* `label` is present only when the edge carries a centre label
|
|
100
|
+
* `label` is present only when the edge carries a centre label that layout
|
|
101
|
+
* placed. It carries the same `lp->set` gate as the labels below: dot's
|
|
102
|
+
* label search can leave a centre label unplaced (sentinel pos), and
|
|
103
|
+
* `render()` then declines to draw it.
|
|
101
104
|
*
|
|
102
105
|
* `tailLabel`/`headLabel` are the `taillabel`/`headlabel` port labels. They
|
|
103
106
|
* are present only once the layout has actually *placed* them (C `lp->set`),
|
|
@@ -106,6 +109,12 @@ export interface NodeGeometry {
|
|
|
106
109
|
* type) still holds calloc-zero coordinates, so it is reported as absent
|
|
107
110
|
* rather than as a label at the origin.
|
|
108
111
|
*
|
|
112
|
+
* `xlabel` is the `xlabel` external label, positioned by the force-placement
|
|
113
|
+
* search rather than by spline midpoint arithmetic, so it is not derivable
|
|
114
|
+
* from `label` or `points`. It carries the same `lp->set` gate: a declared
|
|
115
|
+
* xlabel the search could not fit is reported as absent, exactly as
|
|
116
|
+
* `render()` declines to draw it.
|
|
117
|
+
*
|
|
109
118
|
* `sp`/`ep` are the arrow attachment points. When an end carries an arrow the
|
|
110
119
|
* spline is shortened to leave room for it, and the arrow spans from the
|
|
111
120
|
* terminal control point out to this point — so a consumer drawing its own
|
|
@@ -119,7 +128,8 @@ export interface NodeGeometry {
|
|
|
119
128
|
* (measured: ~1.5pt at `penwidth=1`, ~6.2pt at `penwidth=5`), so `ep` is the
|
|
120
129
|
* point to draw an arrow *to*, not a copy of the rendered polygon's tip.
|
|
121
130
|
*
|
|
122
|
-
* @see lib/common/types.h:ED_spl, ED_label, ED_tail_label, ED_head_label
|
|
131
|
+
* @see lib/common/types.h:ED_spl, ED_label, ED_tail_label, ED_head_label,
|
|
132
|
+
* ED_xlabel
|
|
123
133
|
* @see lib/common/types.h:bezier (sflag/eflag, sp/ep)
|
|
124
134
|
*/
|
|
125
135
|
export interface EdgeGeometry {
|
|
@@ -137,7 +147,7 @@ export interface EdgeGeometry {
|
|
|
137
147
|
* @see lib/common/types.h:bezier.ep
|
|
138
148
|
*/
|
|
139
149
|
ep?: { x: number; y: number };
|
|
140
|
-
/** Centre edge label position, if
|
|
150
|
+
/** Centre edge label position, if placed. @see lib/common/types.h:ED_label */
|
|
141
151
|
label?: { x: number; y: number };
|
|
142
152
|
/**
|
|
143
153
|
* `taillabel` position, if placed.
|
|
@@ -149,6 +159,11 @@ export interface EdgeGeometry {
|
|
|
149
159
|
* @see lib/common/types.h:ED_head_label
|
|
150
160
|
*/
|
|
151
161
|
headLabel?: { x: number; y: number };
|
|
162
|
+
/**
|
|
163
|
+
* `xlabel` (external label) position, if placed.
|
|
164
|
+
* @see lib/common/types.h:ED_xlabel
|
|
165
|
+
*/
|
|
166
|
+
xlabel?: { x: number; y: number };
|
|
152
167
|
}
|
|
153
168
|
|
|
154
169
|
/**
|
|
@@ -261,7 +276,7 @@ function collectEdgePoints(
|
|
|
261
276
|
}
|
|
262
277
|
|
|
263
278
|
/**
|
|
264
|
-
* Position of
|
|
279
|
+
* Position of an edge label that layout actually placed, else undefined.
|
|
265
280
|
* `set` is C's own "this label has coordinates" flag, and the gate
|
|
266
281
|
* emit_edge_label uses before drawing; an unplaced label still holds the
|
|
267
282
|
* calloc-zero pos and must not be published as geometry.
|
|
@@ -301,8 +316,8 @@ function arrowAttachPoints(
|
|
|
301
316
|
|
|
302
317
|
/**
|
|
303
318
|
* Snapshot one edge's geometry.
|
|
304
|
-
* @see lib/common/types.h:ED_spl, ED_label, ED_tail_label, ED_head_label
|
|
305
|
-
* (textlabel_t.pos), bezier.sp/ep
|
|
319
|
+
* @see lib/common/types.h:ED_spl, ED_label, ED_tail_label, ED_head_label,
|
|
320
|
+
* ED_xlabel (textlabel_t.pos), bezier.sp/ep
|
|
306
321
|
*/
|
|
307
322
|
function snapshotEdge(edge: Edge, flipY: (y: number) => number): EdgeGeometry {
|
|
308
323
|
const geom: EdgeGeometry = {
|
|
@@ -310,14 +325,14 @@ function snapshotEdge(edge: Edge, flipY: (y: number) => number): EdgeGeometry {
|
|
|
310
325
|
head: edge.head.name,
|
|
311
326
|
points: collectEdgePoints(edge, flipY),
|
|
312
327
|
};
|
|
313
|
-
const
|
|
314
|
-
if (
|
|
315
|
-
geom.label = { x: lbl.pos.x, y: flipY(lbl.pos.y) };
|
|
316
|
-
}
|
|
328
|
+
const label = placedLabelPos(edge.info.label, flipY);
|
|
329
|
+
if (label !== undefined) geom.label = label;
|
|
317
330
|
const tailLabel = placedLabelPos(edge.info.tail_label, flipY);
|
|
318
331
|
if (tailLabel !== undefined) geom.tailLabel = tailLabel;
|
|
319
332
|
const headLabel = placedLabelPos(edge.info.head_label, flipY);
|
|
320
333
|
if (headLabel !== undefined) geom.headLabel = headLabel;
|
|
334
|
+
const xlabel = placedLabelPos(edge.info.xlabel, flipY);
|
|
335
|
+
if (xlabel !== undefined) geom.xlabel = xlabel;
|
|
321
336
|
const { sp, ep } = arrowAttachPoints(edge, flipY);
|
|
322
337
|
if (sp !== undefined) geom.sp = sp;
|
|
323
338
|
if (ep !== undefined) geom.ep = ep;
|
package/src/cdt/splay.ts
CHANGED
package/src/common/arm-pow.ts
CHANGED
|
@@ -85,11 +85,11 @@ const POW_LOG_TABLE_BITS = 7;
|
|
|
85
85
|
const N_LOG = 1 << POW_LOG_TABLE_BITS;
|
|
86
86
|
const EXP_TABLE_BITS = 7;
|
|
87
87
|
const N_EXP = 1 << EXP_TABLE_BITS;
|
|
88
|
-
const OFF =
|
|
88
|
+
const OFF = BigInt('0x3fe6955500000000');
|
|
89
89
|
|
|
90
90
|
/** Bit patterns and sign bias for the pow special-case branch. */
|
|
91
|
-
const INF_BITS =
|
|
92
|
-
const ONE_BITS =
|
|
91
|
+
const INF_BITS = BigInt('0x7ff0000000000000');
|
|
92
|
+
const ONE_BITS = BigInt('0x3ff0000000000000'); // asuint64(1.0)
|
|
93
93
|
/** SIGN_BIAS = 0x800 << EXP_TABLE_BITS. @see ARM pow.c */
|
|
94
94
|
const SIGN_BIAS = 0x800 << EXP_TABLE_BITS;
|
|
95
95
|
|
|
@@ -218,16 +218,6 @@ export function resetHtmlAnchorIds(): void {
|
|
|
218
218
|
anchorEnv.imgscale = 'false';
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
-
/** Normalise AnchorData to plain strings (empty when absent). */
|
|
222
|
-
function normaliseAnchor(data: AnchorData): { h: string; t: string; tg: string; i: string } {
|
|
223
|
-
return {
|
|
224
|
-
h: data.href !== undefined ? data.href : '',
|
|
225
|
-
t: data.title !== undefined ? data.title : '',
|
|
226
|
-
tg: data.target !== undefined ? data.target : '',
|
|
227
|
-
i: data.id !== undefined ? data.id : '',
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
|
|
231
221
|
/**
|
|
232
222
|
* Open an anchor when the data carries href/title/target.
|
|
233
223
|
*
|
package/src/common/random.ts
CHANGED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EPL-2.0
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* shape_clip: clip a Bezier to a node's own shape boundary.
|
|
5
|
+
*
|
|
6
|
+
* Separate from splines-clip.ts (clip_and_install's home) only for file size;
|
|
7
|
+
* both drive the same node insidefn through bezierClip.
|
|
8
|
+
*
|
|
9
|
+
* @see lib/common/splines.c:shape_clip
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { Point } from '../model/geom.js';
|
|
13
|
+
import type { Node } from '../model/node.js';
|
|
14
|
+
import { bezierClip } from './splines-geom.js';
|
|
15
|
+
import type { InsideContext } from './splines-geom.js';
|
|
16
|
+
|
|
17
|
+
type InsideFn = (c: InsideContext, p: Point) => boolean;
|
|
18
|
+
type ShapeHost = { shape?: { fns?: { insidefn?: InsideFn | null } | null } | null };
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Clip curve to n's boundary in node-relative coordinates, then translate
|
|
22
|
+
* back; ND_rw is restored because some insidefns (record) scribble on it.
|
|
23
|
+
* @see lib/common/splines.c:shape_clip0
|
|
24
|
+
*/
|
|
25
|
+
function shapeClip0(ctx: InsideContext, insidefn: InsideFn, n: Node, curve: Point[], leftInside: boolean): void {
|
|
26
|
+
const saveRealSize = n.info.rw;
|
|
27
|
+
const c = curve.map((p) => ({ x: p.x - n.info.coord.x, y: p.y - n.info.coord.y }));
|
|
28
|
+
bezierClip(ctx, insidefn, c, leftInside);
|
|
29
|
+
for (let i = 0; i < 4; i++) {
|
|
30
|
+
curve[i] = { x: c[i].x + n.info.coord.x, y: c[i].y + n.info.coord.y };
|
|
31
|
+
}
|
|
32
|
+
n.info.rw = saveRealSize;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Clip curve (4 control points, graph coordinates) to node n's shape, in
|
|
37
|
+
* place. Nodes whose shape has no insidefn are left untouched.
|
|
38
|
+
* @see lib/common/splines.c:shape_clip
|
|
39
|
+
*/
|
|
40
|
+
export function shapeClip(n: Node, curve: Point[]): void {
|
|
41
|
+
const insidefn = (n.info as unknown as ShapeHost).shape?.fns?.insidefn ?? null;
|
|
42
|
+
if (insidefn === null) return;
|
|
43
|
+
const ctx: InsideContext = { nodeCoord: n.info.coord, rw: n.info.rw, bp: null, node: n };
|
|
44
|
+
const saveRealSize = n.info.rw;
|
|
45
|
+
const leftInside = insidefn(ctx, { x: curve[0].x - n.info.coord.x, y: curve[0].y - n.info.coord.y });
|
|
46
|
+
n.info.rw = saveRealSize;
|
|
47
|
+
shapeClip0(ctx, insidefn, n, curve, leftInside);
|
|
48
|
+
}
|
|
@@ -52,7 +52,7 @@ export function dotneatoClosest(spl: SplLike, pt: Point): Point {
|
|
|
52
52
|
const c = [bz.list[j], bz.list[j + 1], bz.list[j + 2], bz.list[j + 3]];
|
|
53
53
|
let lo = 0.0; let hi = 1.0;
|
|
54
54
|
let dlo2 = dist2(c[0], pt); let dhi2 = dist2(c[3], pt);
|
|
55
|
-
let pt2
|
|
55
|
+
let pt2: Point;
|
|
56
56
|
for (;;) {
|
|
57
57
|
const t = (lo + hi) / 2.0;
|
|
58
58
|
pt2 = evalBez4(c, t);
|
|
@@ -182,7 +182,7 @@ export function circoLayout(g: Graph): void {
|
|
|
182
182
|
dg.components = comps;
|
|
183
183
|
let blockCount = 0;
|
|
184
184
|
if (comps.length === 1) {
|
|
185
|
-
|
|
185
|
+
circularLayout(comps[0]!, g, blockCount, allEdges);
|
|
186
186
|
copyPositions(comps[0]!);
|
|
187
187
|
// C: adjustNodes(g) on the REAL graph — overlap=false resolves to
|
|
188
188
|
// PRISM on the GTS reference build. @see circularinit.c:203
|
|
@@ -298,15 +298,27 @@ function hasSidePort(e: GraphEdge): boolean {
|
|
|
298
298
|
return (tp.side ?? 0) !== 0 || (hp.side ?? 0) !== 0;
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
-
/**
|
|
301
|
+
/** C's make_flat_adj_edges port test: either end has a defined port, of any
|
|
302
|
+
* side (an interior HTML cell's port has side 0).
|
|
303
|
+
* @see lib/dotgen/dotsplines.c:make_flat_adj_edges (`ports = true`) */
|
|
304
|
+
function hasDefinedPort(e: GraphEdge): boolean {
|
|
305
|
+
return e.info.tail_port.defined || e.info.head_port.defined;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** True when e is a same-rank adjacent flat edge with a defined port — the
|
|
309
|
+
* make_flat_adj_edges rotated-aux case. */
|
|
310
|
+
function isAdjacentPortFlat(e: GraphEdge, g: Graph): boolean {
|
|
311
|
+
return e.tail.info.rank !== undefined && e.tail.info.rank === e.head.info.rank
|
|
312
|
+
&& isFlatAdjacent(g, e) && hasDefinedPort(e);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** True when x is an unrouted adjacent same-rank flat edge with a port. */
|
|
302
316
|
function isGroupableFlat(x: GraphEdge, g: Graph): boolean {
|
|
303
|
-
return x.info.spl === undefined
|
|
304
|
-
&& x.tail.info.rank === x.head.info.rank
|
|
305
|
-
&& isFlatAdjacent(g, x) && hasSidePort(x);
|
|
317
|
+
return x.info.spl === undefined && isAdjacentPortFlat(x, g);
|
|
306
318
|
}
|
|
307
319
|
|
|
308
320
|
/**
|
|
309
|
-
* Collect all unrouted adjacent same-rank
|
|
321
|
+
* Collect all unrouted adjacent same-rank ported flat edges between e's two
|
|
310
322
|
* endpoints (either direction), ordered so group[0].tail is the lower-order
|
|
311
323
|
* (left) node. C's dispatch loop sorts edges by edgecmp (equivalent edges
|
|
312
324
|
* contiguous) and groups a run while `getmainedge(e0) === getmainedge(e1)`
|
|
@@ -399,7 +411,7 @@ function routeFlatEdge(e: GraphEdge, g: Graph): boolean {
|
|
|
399
411
|
// other edge, so only labeled flats are diverted. @see dotsplines.c:1527-1533
|
|
400
412
|
if (makeFlatLabeledEdge(g, e)) return true;
|
|
401
413
|
if (makeAdjFlatNoPortEdge(g, e)) return true;
|
|
402
|
-
if (hasSidePort(e) && routeFaithfulSidePort(e, g)) return true;
|
|
414
|
+
if ((hasSidePort(e) || isAdjacentPortFlat(e, g)) && routeFaithfulSidePort(e, g)) return true;
|
|
403
415
|
// Non-adjacent unlabeled flat with no side port (e.g. a same-rank cross-cluster
|
|
404
416
|
// edge with an intervening node): C make_flat_edge routes it UP through the box
|
|
405
417
|
// corridor, not straight through. @see lib/dotgen/dotsplines.c:make_flat_edge
|
package/src/layout/dot/ns.ts
CHANGED
|
@@ -37,7 +37,6 @@ import { dotRank, isACluster } from './rank.js';
|
|
|
37
37
|
import { dotMincross } from './mincross.js';
|
|
38
38
|
import { dotPosition } from './position.js';
|
|
39
39
|
import { gvPostprocess } from '../../common/postproc.js';
|
|
40
|
-
import { mapArrowOpPoints } from '../../common/arrows-shapes-util.js';
|
|
41
40
|
import type { ArrowDrawOp } from '../../common/arrows-types.js';
|
|
42
41
|
|
|
43
42
|
// ---------------------------------------------------------------------------
|
|
@@ -13,9 +13,7 @@ import type { Node } from '../../model/node.js';
|
|
|
13
13
|
import type { Edge } from '../../model/edge.js';
|
|
14
14
|
import type { Point, Port } from '../../model/geom.js';
|
|
15
15
|
import { VIRTUAL, MC_SCALE } from './fastgr.js';
|
|
16
|
-
import {
|
|
17
|
-
import { bezierClipNode } from './edge-route-clip.js';
|
|
18
|
-
import { nodeInsideFn } from './edge-route-routing.js';
|
|
16
|
+
import { shapeClip } from '../../common/shape-clip.js';
|
|
19
17
|
import { nodesInSeq } from './decomp.js';
|
|
20
18
|
import { cround } from '../../common/arith.js';
|
|
21
19
|
|
|
@@ -51,28 +49,6 @@ export function sameedge(same: SameGroup[], e: Edge, id: string): void {
|
|
|
51
49
|
same.push({ id, edges: [e] });
|
|
52
50
|
}
|
|
53
51
|
|
|
54
|
-
// ---------------------------------------------------------------------------
|
|
55
|
-
// shapeClip — move curve[0] to the node boundary
|
|
56
|
-
// @see lib/dotgen/sameport.c — calls shape_clip(u, curve)
|
|
57
|
-
// ---------------------------------------------------------------------------
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Move curve[0] to node u's shape boundary along the Bézier, exactly as C's
|
|
61
|
-
* shape_clip: build the node's shape inside-function (ellipse vs polygon, pen-
|
|
62
|
-
* width aware) and bezier-clip with left_inside = insidefn(curve[0]). curve[0]
|
|
63
|
-
* is the node center, which is always inside, so left_inside is true.
|
|
64
|
-
*
|
|
65
|
-
* Replaces an earlier rectangle approximation, which was wrong for ellipse
|
|
66
|
-
* nodes (every honda head is an ellipse) and shifted the shared port ~17px.
|
|
67
|
-
*
|
|
68
|
-
* @see lib/common/splines.c:shape_clip / shape_clip0
|
|
69
|
-
*/
|
|
70
|
-
export function shapeClip(u: Node, curve: Point[]): void {
|
|
71
|
-
const box = nodeBoxOf(u, u.root);
|
|
72
|
-
const clipped = bezierClipNode(curve, box.center.x, box.center.y, nodeInsideFn(box), true);
|
|
73
|
-
for (let i = 0; i < curve.length && i < clipped.length; i++) curve[i] = clipped[i];
|
|
74
|
-
}
|
|
75
|
-
|
|
76
52
|
// ---------------------------------------------------------------------------
|
|
77
53
|
// buildPort — construct the shared port for a group
|
|
78
54
|
// @see lib/dotgen/sameport.c:sameport
|
|
@@ -79,9 +79,11 @@ function rankY(g: Graph, r: number): number {
|
|
|
79
79
|
return g.info.rank![r].v[0].info.coord?.y ?? 0;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
/** sizey when n is at the maximum rank.
|
|
82
|
+
/** sizey when n is at the maximum rank. C's `r > 0` is a literal index test;
|
|
83
|
+
* after abomination C's rank is r - abomShift (AD-2 renumbers +1 where C uses
|
|
84
|
+
* index -1). @see lib/dotgen/dotsplines.c:392-396 */
|
|
83
85
|
function sizeyAtMaxrank(g: Graph, n: Node, r: number): number {
|
|
84
|
-
if (r > 0) return rankY(g, r - 1) - (n.info.coord?.y ?? 0);
|
|
86
|
+
if (r - (g.info.abomShift ?? 0) > 0) return rankY(g, r - 1) - (n.info.coord?.y ?? 0);
|
|
85
87
|
return n.info.ht ?? 0;
|
|
86
88
|
}
|
|
87
89
|
|
|
@@ -130,7 +130,7 @@ export function makeFlatLabeledEdge(g: Graph, e: Edge): boolean {
|
|
|
130
130
|
const et = edgeType(g);
|
|
131
131
|
if (et === EDGETYPE_LINE) {
|
|
132
132
|
const ps = flatLabeledLinePoints(e.tail, e.head, e);
|
|
133
|
-
|
|
133
|
+
clipAndInstall(e, e.head, ps, ps.length, buildDotSinfo());
|
|
134
134
|
return true;
|
|
135
135
|
}
|
|
136
136
|
// C make_flat_edge forward-normalizes a backward (tail right of head by node
|
|
@@ -112,8 +112,15 @@ function dispatchEdgeGroup(g: Graph, group: Edge[], ctx: GroupRouteCtx): void {
|
|
|
112
112
|
}
|
|
113
113
|
return;
|
|
114
114
|
}
|
|
115
|
-
if (nodeRankOf(e0.tail) === nodeRankOf(e0.head)) return;
|
|
116
115
|
const uniq = dedupByOrig(group);
|
|
116
|
+
// Flat group: route HERE at its edgecmp position, as C's make_flat_edge call
|
|
117
|
+
// does, so it reads vnodes before later regular edges' recover_slack moves
|
|
118
|
+
// them. The flat routers gather their own group and skip routed members.
|
|
119
|
+
// @see lib/dotgen/dotsplines.c:dot_splines_ (410-411)
|
|
120
|
+
if (nodeRankOf(e0.tail) === nodeRankOf(e0.head)) {
|
|
121
|
+
for (const e of uniq) routeLoneEdge(e, g);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
117
124
|
// Lone edge: route HERE at its edgecmp position (interleaved with groups), as C
|
|
118
125
|
// does, so it reads recover_slack-moved vnodes correctly. @see root-cause.md
|
|
119
126
|
if (uniq.length <= 1) {
|
|
@@ -286,7 +286,7 @@ class Cdt {
|
|
|
286
286
|
let e2: CEdge | null = null; let e3: CEdge | null = null;
|
|
287
287
|
if (f !== null) {
|
|
288
288
|
const r = this.ringFrom(f, prev);
|
|
289
|
-
v1 = r.v1; v2 = r.v2; v3 = r.v3;
|
|
289
|
+
v1 = r.v1; v2 = r.v2; v3 = r.v3; e2 = r.e2; e3 = r.e3;
|
|
290
290
|
}
|
|
291
291
|
while (f !== null) {
|
|
292
292
|
const orient = this.orientPt(ox, oy, v3, x, y);
|
package/src/layout/neato/init.ts
CHANGED
|
@@ -15,7 +15,6 @@ import type { Node } from '../../model/node.js';
|
|
|
15
15
|
import type { Edge } from '../../model/edge.js';
|
|
16
16
|
import type { VtxData } from './dijkstra.js';
|
|
17
17
|
import {
|
|
18
|
-
stressMajorizationKD,
|
|
19
18
|
DFLT_ITERATIONS,
|
|
20
19
|
MODEL_MDS as STRESS_MODEL_MDS,
|
|
21
20
|
MODEL_CIRCUIT as STRESS_MODEL_CIRCUIT,
|
package/src/ortho/ortho-route.ts
CHANGED
|
@@ -12,7 +12,7 @@ import type {
|
|
|
12
12
|
OrthoSegment, Route, Cell, SGraph, SNode, SEdge,
|
|
13
13
|
Channel, ChanDict, Maze,
|
|
14
14
|
} from "./types.js";
|
|
15
|
-
import { Bend
|
|
15
|
+
import { Bend } from "./types.js";
|
|
16
16
|
import { chanSearch, chansInOrder } from "./maze-channels.js";
|
|
17
17
|
import { makeGraph } from "./rawgraph.js";
|
|
18
18
|
import { insertEdge, edgeExists, removeRedge, topSort } from "./rawgraph.js";
|
|
@@ -22,8 +22,6 @@ import { cround } from "../common/arith.js";
|
|
|
22
22
|
|
|
23
23
|
// ─── weight constants ─────────────────────────────────────────────────────────
|
|
24
24
|
|
|
25
|
-
const delta = 1; // weight per unit length
|
|
26
|
-
const mu = 500; // bend penalty per bend edge
|
|
27
25
|
export const BIG = 16384; // near-infinite weight
|
|
28
26
|
|
|
29
27
|
// ─── Segment construction ─────────────────────────────────────────────────────
|
package/src/ortho/trap-query.ts
CHANGED
package/src/render/dot.ts
CHANGED
|
@@ -19,14 +19,8 @@ import type { ArrowDrawOp } from '../common/arrows-types.js';
|
|
|
19
19
|
import type { GVColor } from '../common/color.js';
|
|
20
20
|
import { resolveRenderColor } from './color-resolve.js';
|
|
21
21
|
import { edgeIsTapered } from './svg-tapered-edge.js';
|
|
22
|
-
import { taper, taperfun } from '../common/taper.js';
|
|
23
|
-
import { orthoRoundedRadius } from './svg-helpers.js';
|
|
24
|
-
import { orthoRoundedPolylines } from './svg-edge-ortho-radius.js';
|
|
25
22
|
import { findStopColor, parseStyleFlags } from '../common/style-resolve.js';
|
|
26
23
|
import { parseGraphPad } from '../gvc/viewport.js';
|
|
27
|
-
import { parseSegs } from '../common/multicolor.js';
|
|
28
|
-
import { splitSplineByColor } from './svg-edge-split.js';
|
|
29
|
-
import { buildOffsetLists, advanceTmpList } from '../common/edge-offset.js';
|
|
30
24
|
import { edgeHasDrawableContent } from './svg.js';
|
|
31
25
|
import type { Bezier } from '../model/geom.js';
|
|
32
26
|
import type { RendererPlugin } from '../gvc/context.js';
|
|
@@ -41,11 +35,10 @@ import { renderEdgeLabels } from '../gvc/edge-labels.js';
|
|
|
41
35
|
export * from './dot/xdot-ops.js';
|
|
42
36
|
export * from './dot/attrs.js';
|
|
43
37
|
import {
|
|
44
|
-
NON_LINE_STYLES,
|
|
45
|
-
|
|
46
|
-
|
|
38
|
+
NON_LINE_STYLES, escBackslash, linearGradientOp,
|
|
39
|
+
makeXbufs, radialGradientOp, trimFixed3, utf8Len, xdotFillColor, xdotFont,
|
|
40
|
+
xdotNum, xdotPenColor, xdotPoint, xdotPoints, xdotStrOp,
|
|
47
41
|
} from './dot/xdot-ops.js';
|
|
48
|
-
import { isDirected } from './dot/attrs.js';
|
|
49
42
|
|
|
50
43
|
|
|
51
44
|
// ---------------------------------------------------------------------------
|
package/src/vpsc/Solver.ts
CHANGED