@knowvah/dot-engine 1.9.0 → 2.0.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/README.md +232 -30
- package/dist/api/builder.d.ts +3 -0
- package/dist/api/builder.d.ts.map +1 -1
- package/dist/api/edge-ops.d.ts +7 -0
- package/dist/api/edge-ops.d.ts.map +1 -1
- package/dist/api/geometry.d.ts +5 -2
- package/dist/api/geometry.d.ts.map +1 -1
- package/dist/api.js +159 -31
- package/dist/api.js.map +3 -3
- package/dist/async/collect.d.ts +49 -0
- package/dist/async/collect.d.ts.map +1 -0
- package/dist/async/fonts.d.ts +20 -0
- package/dist/async/fonts.d.ts.map +1 -0
- package/dist/async/render-async.d.ts +91 -0
- package/dist/async/render-async.d.ts.map +1 -0
- package/dist/async/render-into.d.ts +38 -0
- package/dist/async/render-into.d.ts.map +1 -0
- package/dist/async/sanitize.d.ts +28 -0
- package/dist/async/sanitize.d.ts.map +1 -0
- package/dist/common/htmltable-types.d.ts +3 -3
- package/dist/common/htmltable-types.d.ts.map +1 -1
- package/dist/common/make-label.d.ts.map +1 -1
- package/dist/common/poly-shapes.d.ts.map +1 -1
- package/dist/common/textmeasure-factory.d.ts +2 -0
- package/dist/common/textmeasure-factory.d.ts.map +1 -1
- package/dist/common/utils-inputscale.d.ts +19 -0
- package/dist/common/utils-inputscale.d.ts.map +1 -0
- package/dist/errors.d.ts +61 -5
- package/dist/errors.d.ts.map +1 -1
- package/dist/gvc/context.d.ts +36 -4
- package/dist/gvc/context.d.ts.map +1 -1
- package/dist/gvc/device.d.ts +5 -2
- package/dist/gvc/device.d.ts.map +1 -1
- package/dist/gvc/image-resolver.d.ts +16 -16
- package/dist/gvc/image-resolver.d.ts.map +1 -1
- package/dist/gvc/job.d.ts +1 -9
- package/dist/gvc/job.d.ts.map +1 -1
- package/dist/gvc/usershape.d.ts +2 -12
- package/dist/gvc/usershape.d.ts.map +1 -1
- package/dist/index.d.ts +27 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7682 -5535
- package/dist/index.js.map +4 -4
- package/dist/label/index.d.ts.map +1 -1
- package/dist/label/node.d.ts +0 -6
- package/dist/label/node.d.ts.map +1 -1
- package/dist/label/rectangle.d.ts +1 -7
- package/dist/label/rectangle.d.ts.map +1 -1
- package/dist/layout/circo/circular.d.ts +8 -5
- package/dist/layout/circo/circular.d.ts.map +1 -1
- package/dist/layout/dot/pack-components.d.ts +0 -19
- package/dist/layout/dot/pack-components.d.ts.map +1 -1
- package/dist/layout/dot/position.d.ts +7 -2
- package/dist/layout/dot/position.d.ts.map +1 -1
- package/dist/layout/fdp/derive.d.ts.map +1 -1
- package/dist/layout/fdp/index.d.ts.map +1 -1
- package/dist/layout/fdp/init.d.ts.map +1 -1
- package/dist/layout/fdp/layout.d.ts.map +1 -1
- package/dist/layout/fdp/normalize.d.ts +2 -1
- package/dist/layout/fdp/normalize.d.ts.map +1 -1
- package/dist/layout/fdp/ports.d.ts +0 -10
- package/dist/layout/fdp/ports.d.ts.map +1 -1
- package/dist/layout/fdp/xlayout.d.ts +0 -16
- package/dist/layout/fdp/xlayout.d.ts.map +1 -1
- package/dist/layout/neato/adjust-info.d.ts +117 -0
- package/dist/layout/neato/adjust-info.d.ts.map +1 -0
- package/dist/layout/neato/cdt-surface.d.ts.map +1 -1
- package/dist/layout/neato/constraint-adjust.d.ts +40 -0
- package/dist/layout/neato/constraint-adjust.d.ts.map +1 -0
- package/dist/layout/neato/edge-len.d.ts +20 -0
- package/dist/layout/neato/edge-len.d.ts.map +1 -0
- package/dist/layout/neato/fdp-adjust.d.ts +32 -4
- package/dist/layout/neato/fdp-adjust.d.ts.map +1 -1
- package/dist/layout/neato/index.d.ts +9 -6
- package/dist/layout/neato/index.d.ts.map +1 -1
- package/dist/layout/neato/init.d.ts +4 -11
- package/dist/layout/neato/init.d.ts.map +1 -1
- package/dist/layout/neato/kk-paths.d.ts +50 -0
- package/dist/layout/neato/kk-paths.d.ts.map +1 -0
- package/dist/layout/neato/kk-solve.d.ts +14 -0
- package/dist/layout/neato/kk-solve.d.ts.map +1 -0
- package/dist/layout/neato/kk.d.ts +44 -0
- package/dist/layout/neato/kk.d.ts.map +1 -0
- package/dist/layout/neato/multispline-router.d.ts.map +1 -1
- package/dist/layout/neato/poly.d.ts +50 -0
- package/dist/layout/neato/poly.d.ts.map +1 -0
- package/dist/layout/neato/sc-adjust.d.ts +2 -9
- package/dist/layout/neato/sc-adjust.d.ts.map +1 -1
- package/dist/layout/neato/sgd-dijkstra.d.ts +20 -0
- package/dist/layout/neato/sgd-dijkstra.d.ts.map +1 -0
- package/dist/layout/neato/sgd.d.ts +14 -9
- package/dist/layout/neato/sgd.d.ts.map +1 -1
- package/dist/layout/neato/simple-scale.d.ts +15 -0
- package/dist/layout/neato/simple-scale.d.ts.map +1 -0
- package/dist/layout/neato/start.d.ts +51 -0
- package/dist/layout/neato/start.d.ts.map +1 -0
- package/dist/layout/neato/vpsc-adjust.d.ts +21 -0
- package/dist/layout/neato/vpsc-adjust.d.ts.map +1 -0
- package/dist/layout/sfdp/index.d.ts.map +1 -1
- package/dist/layout/sfdp/init.d.ts +0 -9
- package/dist/layout/sfdp/init.d.ts.map +1 -1
- package/dist/layout/sfdp/spring-driver.d.ts.map +1 -1
- package/dist/layout/twopi/circle.d.ts +0 -7
- package/dist/layout/twopi/circle.d.ts.map +1 -1
- package/dist/ortho/ortho-parallel.d.ts.map +1 -1
- package/dist/ortho/trap-query.d.ts.map +1 -1
- package/dist/parser/index.d.ts +9 -5
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/render/index.d.ts +2 -0
- package/dist/render/index.d.ts.map +1 -1
- package/dist/render/public.d.ts +9 -3
- package/dist/render/public.d.ts.map +1 -1
- package/dist/render/xdot-public.d.ts +7 -2
- package/dist/render/xdot-public.d.ts.map +1 -1
- package/dist/render.js +7468 -5556
- package/dist/render.js.map +4 -4
- package/dist/util/xml.d.ts.map +1 -1
- package/dist/vpsc/Solver.d.ts +1 -0
- package/dist/vpsc/Solver.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/api/builder.ts +73 -5
- package/src/api/edge-ops.ts +19 -0
- package/src/api/geometry.ts +22 -5
- package/src/async/collect.ts +204 -0
- package/src/async/fonts.ts +61 -0
- package/src/async/render-async.ts +205 -0
- package/src/async/render-into.ts +115 -0
- package/src/async/sanitize.ts +150 -0
- package/src/common/htmltable-types.ts +4 -5
- package/src/common/make-label.ts +10 -1
- package/src/common/poly-shapes.ts +4 -1
- package/src/common/textmeasure-factory.ts +11 -0
- package/src/common/utils-inputscale.ts +31 -0
- package/src/errors.ts +188 -5
- package/src/gvc/context.ts +96 -17
- package/src/gvc/device.ts +11 -6
- package/src/gvc/image-resolver.ts +25 -2
- package/src/gvc/job.ts +3 -1
- package/src/gvc/usershape.ts +6 -0
- package/src/index.ts +59 -43
- package/src/label/index.ts +6 -2
- package/src/label/node.ts +2 -1
- package/src/label/rectangle.ts +4 -2
- package/src/layout/circo/circular.ts +9 -6
- package/src/layout/dot/pack-components.ts +6 -2
- package/src/layout/dot/position.ts +21 -3
- package/src/layout/fdp/derive.ts +7 -6
- package/src/layout/fdp/index.ts +45 -5
- package/src/layout/fdp/init.ts +7 -6
- package/src/layout/fdp/layout.ts +2 -1
- package/src/layout/fdp/normalize.ts +12 -8
- package/src/layout/fdp/ports.ts +3 -2
- package/src/layout/fdp/xlayout.ts +8 -5
- package/src/layout/neato/adjust-info.ts +332 -0
- package/src/layout/neato/cdt-surface.ts +50 -30
- package/src/layout/neato/constraint-adjust.ts +466 -0
- package/src/layout/neato/edge-len.ts +36 -0
- package/src/layout/neato/fdp-adjust.ts +120 -12
- package/src/layout/neato/index.ts +36 -54
- package/src/layout/neato/init.ts +21 -36
- package/src/layout/neato/kk-paths.ts +146 -0
- package/src/layout/neato/kk-solve.ts +64 -0
- package/src/layout/neato/kk.ts +322 -0
- package/src/layout/neato/multispline-router.ts +4 -3
- package/src/layout/neato/poly.ts +493 -0
- package/src/layout/neato/sc-adjust.ts +2 -16
- package/src/layout/neato/sgd-dijkstra.ts +125 -0
- package/src/layout/neato/sgd.ts +63 -40
- package/src/layout/neato/simple-scale.ts +54 -0
- package/src/layout/neato/start.ts +222 -0
- package/src/layout/neato/vpsc-adjust.ts +93 -0
- package/src/layout/sfdp/index.ts +4 -5
- package/src/layout/sfdp/init.ts +40 -4
- package/src/layout/sfdp/spring-driver.ts +30 -1
- package/src/layout/twopi/circle.ts +2 -1
- package/src/ortho/ortho-parallel.ts +2 -1
- package/src/ortho/trap-query.ts +2 -1
- package/src/parser/index.ts +11 -11
- package/src/render/index.ts +5 -0
- package/src/render/public.ts +24 -24
- package/src/render/svg.ts +1 -1
- package/src/render/xdot-public.ts +19 -18
- package/src/util/xml.ts +24 -30
- package/src/vpsc/Solver.ts +5 -3
package/src/index.ts
CHANGED
|
@@ -10,46 +10,42 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { parse } from './parser/index.js';
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
InternalError,
|
|
15
|
+
invalidArgType,
|
|
16
|
+
isGvError,
|
|
17
|
+
isUsageError,
|
|
18
|
+
messageOf,
|
|
19
|
+
rethrowAtBoundary,
|
|
20
|
+
} from './errors.js';
|
|
14
21
|
import type { GvError, RenderResult } from './errors.js';
|
|
15
22
|
import type { EngineName } from './gvc/context.js';
|
|
16
23
|
import { render as deviceRender } from './gvc/device.js';
|
|
17
24
|
import { createDefaultContext } from './gvc/default-context.js';
|
|
18
25
|
|
|
19
|
-
/**
|
|
20
|
-
* Duck-type a thrown value as a {@link GvError}: an object carrying a string
|
|
21
|
-
* `type` and a string `code`. Covers `ParseError`, `HtmlParseError`, and
|
|
22
|
-
* `RenderError` without per-subclass `instanceof`.
|
|
23
|
-
*/
|
|
24
|
-
function isGvErrorLike(err: unknown): err is GvError {
|
|
25
|
-
return (
|
|
26
|
-
typeof err === 'object' &&
|
|
27
|
-
err !== null &&
|
|
28
|
-
typeof (err as { type?: unknown }).type === 'string' &&
|
|
29
|
-
typeof (err as { code?: unknown }).code === 'string'
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
26
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
27
|
+
|
|
28
|
+
/** Reject non-string `dotSource` / `engine` before any work starts. */
|
|
29
|
+
function checkRenderArgs(dotSource: unknown, engine: unknown): void {
|
|
30
|
+
if (typeof dotSource !== 'string') {
|
|
31
|
+
throw invalidArgType('dotSource', 'string', dotSource);
|
|
32
|
+
}
|
|
33
|
+
if (typeof engine !== 'string') {
|
|
34
|
+
throw invalidArgType('engine', 'string', engine);
|
|
35
|
+
}
|
|
42
36
|
}
|
|
43
|
-
/* v8 ignore stop */
|
|
44
37
|
|
|
45
38
|
/**
|
|
46
|
-
* Normalize
|
|
47
|
-
* (no stack).
|
|
39
|
+
* Normalize a thrown {@link GvError} to a plain, JSON-serializable data object
|
|
40
|
+
* (no stack, no `cause`). A value that is not a GvError is wrapped as an
|
|
41
|
+
* `InternalError` first.
|
|
48
42
|
*/
|
|
49
43
|
function classifyError(err: unknown): GvError {
|
|
50
|
-
/* v8 ignore next --
|
|
51
|
-
|
|
52
|
-
const gv: GvError =
|
|
44
|
+
/* v8 ignore next 3 -- renderSvg already normalizes every non-usage throw to
|
|
45
|
+
a GvError, so only a throw escaping its context setup could land here. */
|
|
46
|
+
const gv: GvError = isGvError(err)
|
|
47
|
+
? err
|
|
48
|
+
: new InternalError(messageOf(err), { cause: err });
|
|
53
49
|
const out: GvError = {
|
|
54
50
|
type: gv.type, code: gv.code, message: gv.message, friendlyMessage: gv.friendlyMessage,
|
|
55
51
|
};
|
|
@@ -61,9 +57,14 @@ function classifyError(err: unknown): GvError {
|
|
|
61
57
|
/**
|
|
62
58
|
* Render a DOT-language string to SVG using the specified layout engine.
|
|
63
59
|
*
|
|
64
|
-
*
|
|
65
|
-
* `
|
|
66
|
-
*
|
|
60
|
+
* Throws a {@link DotEngineError} for any problem with the input: `ParseError`
|
|
61
|
+
* (`SYNTAX_*`, `EDGE_OP_*`, `GENERIC_ERROR` for nesting too deep) for invalid
|
|
62
|
+
* DOT, `RenderError`
|
|
63
|
+
* (`RENDER_ERROR`, `UNKNOWN_LAYOUT`, `UNSUPPORTED_FEATURE`) for layout/render
|
|
64
|
+
* failures, and `InternalError` (`INTERNAL_ERROR`, `cause` = original) for a
|
|
65
|
+
* dot-engine bug. Invalid arguments throw a `TypeError` carrying a `code`. A
|
|
66
|
+
* malformed HTML-like label does not throw: as in C (htmlparse.y YYABORT) the
|
|
67
|
+
* label renders empty.
|
|
67
68
|
*
|
|
68
69
|
* @remarks
|
|
69
70
|
* Security: when `dotSource` is untrusted, treat the returned SVG as
|
|
@@ -78,15 +79,21 @@ function classifyError(err: unknown): GvError {
|
|
|
78
79
|
* ('dot', 'neato', 'fdp', 'sfdp', 'circo', 'twopi',
|
|
79
80
|
* 'osage', 'patchwork') or any custom-registered name
|
|
80
81
|
* @returns SVG string
|
|
81
|
-
* @throws
|
|
82
|
-
* @throws
|
|
82
|
+
* @throws TypeError `ERR_INVALID_ARG_TYPE` if `dotSource` or `engine` is not a string
|
|
83
|
+
* @throws TypeError `ERR_INVALID_ARG_VALUE` if `engine` names no registered engine
|
|
84
|
+
* @throws ParseError `SYNTAX_ERROR`, `SYNTAX_UNEXPECTED_EOF`,
|
|
85
|
+
* `EDGE_OP_DIRECTED_IN_UNDIRECTED`, `EDGE_OP_UNDIRECTED_IN_DIRECTED` if
|
|
86
|
+
* `dotSource` is not valid DOT
|
|
87
|
+
* @throws RenderError `RENDER_ERROR`, `UNKNOWN_LAYOUT` or `UNSUPPORTED_FEATURE`
|
|
88
|
+
* if layout or rendering fails
|
|
89
|
+
* @throws InternalError `INTERNAL_ERROR` on a dot-engine bug
|
|
83
90
|
*/
|
|
84
91
|
export function renderSvg(dotSource: string, engine: EngineName): string {
|
|
92
|
+
checkRenderArgs(dotSource, engine);
|
|
85
93
|
const ctx = createDefaultContext();
|
|
86
94
|
try {
|
|
87
95
|
// parse() is inside the try so any non-ParseError throw (e.g. a raw
|
|
88
|
-
// RangeError from stack exhaustion) is still normalized
|
|
89
|
-
// honoring the "always throws a value implementing GvError" contract.
|
|
96
|
+
// RangeError from stack exhaustion) is still normalized (InternalError).
|
|
90
97
|
const g = parse(dotSource);
|
|
91
98
|
ctx.layout(g, engine);
|
|
92
99
|
const svg = deviceRender(ctx, g, 'svg');
|
|
@@ -94,37 +101,40 @@ export function renderSvg(dotSource: string, engine: EngineName): string {
|
|
|
94
101
|
ctx.freeLayout(g, engine);
|
|
95
102
|
return svg;
|
|
96
103
|
} catch (err: unknown) {
|
|
97
|
-
|
|
98
|
-
// is re-surfaced unchanged; only genuinely-unknown throws become RENDER_ERROR.
|
|
99
|
-
/* v8 ignore next -- current engines don't throw a GvError-like value here */
|
|
100
|
-
if (isGvErrorLike(err)) throw err;
|
|
101
|
-
throw new RenderError(messageOf(err), 'RENDER_ERROR');
|
|
104
|
+
return rethrowAtBoundary(err);
|
|
102
105
|
}
|
|
103
106
|
}
|
|
104
107
|
|
|
105
108
|
/**
|
|
106
109
|
* Result-style render: returns `{ svg }` on success or `{ errors: [one] }` on
|
|
107
110
|
* the first failure (svg XOR errors). Errors are plain JSON-serializable
|
|
108
|
-
* {@link GvError} data objects.
|
|
111
|
+
* {@link GvError} data objects. Returns (never throws) for any DOT input and
|
|
112
|
+
* any failure of the graph itself; throws only for invalid arguments.
|
|
109
113
|
*
|
|
110
114
|
* @remarks
|
|
111
115
|
* Security: same untrusted-input caveat as {@link renderSvg} — the returned
|
|
112
116
|
* `svg` is attacker-controlled markup for untrusted `dotSource`; apply a CSP or
|
|
113
117
|
* sanitize before embedding. See the README "Security" section.
|
|
118
|
+
*
|
|
119
|
+
* @throws TypeError `ERR_INVALID_ARG_TYPE` if `dotSource` or `engine` is not a string
|
|
120
|
+
* @throws TypeError `ERR_INVALID_ARG_VALUE` if `engine` names no registered engine
|
|
114
121
|
*/
|
|
115
122
|
export function tryRenderSvg(dotSource: string, engine: EngineName): RenderResult {
|
|
123
|
+
checkRenderArgs(dotSource, engine);
|
|
116
124
|
try {
|
|
117
125
|
return { svg: renderSvg(dotSource, engine) };
|
|
118
126
|
} catch (err: unknown) {
|
|
127
|
+
if (isUsageError(err)) throw err;
|
|
119
128
|
return { errors: [classifyError(err)] };
|
|
120
129
|
}
|
|
121
130
|
}
|
|
122
131
|
|
|
123
132
|
export { parse } from './parser/index.js';
|
|
124
133
|
export { ParseError } from './parser/index.js';
|
|
125
|
-
export { RenderError } from './errors.js';
|
|
134
|
+
export { DotEngineError, InternalError, RenderError, isGvError } from './errors.js';
|
|
126
135
|
export type {
|
|
127
136
|
GvError,
|
|
137
|
+
UsageErrorCode,
|
|
128
138
|
GvErrorType,
|
|
129
139
|
GvErrorCode,
|
|
130
140
|
GvExpectation,
|
|
@@ -163,3 +173,9 @@ export { render as renderWithContext } from './gvc/device.js';
|
|
|
163
173
|
// rather than `render`. See decisions.md ADR-5 and the decision journal.
|
|
164
174
|
export * from './api/index.js';
|
|
165
175
|
export * from './render/index.js';
|
|
176
|
+
|
|
177
|
+
// Async API (async-api ADR-7): renderAsync is re-exported via ./render above.
|
|
178
|
+
export { renderSvgAsync } from './async/render-async.js';
|
|
179
|
+
export type { AsyncSvgOptions, AsyncSvgResult } from './async/render-async.js';
|
|
180
|
+
export { renderSvgInto } from './async/render-into.js';
|
|
181
|
+
export type { RenderSvgIntoOptions, RenderSvgIntoResult } from './async/render-into.js';
|
package/src/label/index.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { type Rect, overlap, combineRect, NUMDIMS } from './rectangle.js';
|
|
10
|
+
import { InternalError } from '../errors.js';
|
|
10
11
|
import {
|
|
11
12
|
NODECARD,
|
|
12
13
|
type Branch,
|
|
@@ -256,11 +257,14 @@ function rTreeInsert2(
|
|
|
256
257
|
return 0; // should never happen
|
|
257
258
|
}
|
|
258
259
|
|
|
259
|
-
/**
|
|
260
|
+
/**
|
|
261
|
+
* Validate rect: low side must be <= high side per dimension.
|
|
262
|
+
* @see label/index.c:RTreeInsert (assert at index.c:178)
|
|
263
|
+
*/
|
|
260
264
|
function validateRect(r: Rect): void {
|
|
261
265
|
for (let i = 0; i < NUMDIMS; i++) {
|
|
262
266
|
if (r.boundary[i] > r.boundary[NUMDIMS + i]) {
|
|
263
|
-
throw new
|
|
267
|
+
throw new InternalError('rTreeInsert: rect low > high');
|
|
264
268
|
}
|
|
265
269
|
}
|
|
266
270
|
}
|
package/src/label/node.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* @see label/index.h
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { InternalError } from '../errors.js';
|
|
10
11
|
import { type Rect, initRect, combineRect, rectArea, nullRect } from './rectangle.js';
|
|
11
12
|
|
|
12
13
|
// C node.c uses uint64_t for bestIncr, bestArea, increase — subtraction wraps.
|
|
@@ -261,7 +262,7 @@ let _splitNodeImpl: (
|
|
|
261
262
|
b: Branch,
|
|
262
263
|
newNodeOut: { value: Node | null },
|
|
263
264
|
) => void = () => {
|
|
264
|
-
throw new
|
|
265
|
+
throw new InternalError('splitNode not yet registered — import split-q.ts first');
|
|
265
266
|
};
|
|
266
267
|
|
|
267
268
|
/** Register the splitNode implementation. Called by split-q.ts at load time. */
|
package/src/label/rectangle.ts
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* @see label/index.h
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { RenderError } from '../errors.js';
|
|
11
|
+
|
|
10
12
|
/** @see label/index.h:NUMDIMS */
|
|
11
13
|
export const NUMDIMS = 2;
|
|
12
14
|
|
|
@@ -71,7 +73,7 @@ export function nullRect(): Rect {
|
|
|
71
73
|
* Returns 0 for undefined rects or zero-width dimensions.
|
|
72
74
|
* Throws if area overflows Number.MAX_SAFE_INTEGER (browser-safe substitute
|
|
73
75
|
* for C's graphviz_exit on UINT64_MAX overflow).
|
|
74
|
-
* @see label/rectangle.c:RectArea
|
|
76
|
+
* @see label/rectangle.c:RectArea (agerrorf + graphviz_exit at rectangle.c:73)
|
|
75
77
|
*/
|
|
76
78
|
export function rectArea(r: Rect): number {
|
|
77
79
|
if (isUndefined(r)) return 0;
|
|
@@ -81,7 +83,7 @@ export function rectArea(r: Rect): number {
|
|
|
81
83
|
const dim = r.boundary[i + NUMDIMS] - r.boundary[i];
|
|
82
84
|
if (dim === 0) return 0;
|
|
83
85
|
if (Number.MAX_SAFE_INTEGER / dim < area) {
|
|
84
|
-
throw new
|
|
86
|
+
throw new RenderError('label: area too large for rtree', 'RENDER_ERROR');
|
|
85
87
|
}
|
|
86
88
|
area *= dim;
|
|
87
89
|
}
|
|
@@ -159,14 +159,17 @@ export function copyPositions(_sg: SubGraph): void {
|
|
|
159
159
|
// ---------------------------------------------------------------------------
|
|
160
160
|
|
|
161
161
|
/**
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
162
|
+
* Per-component adjustNodes(sg). C calls it on each DERIVED component
|
|
163
|
+
* (circularinit.c:212), but the derived graph is a fresh root that never
|
|
164
|
+
* declares `overlap`, `normalize` or `scale`: agget(sg, "overlap") is NULL
|
|
165
|
+
* (AM_NONE) and removeOverlapWith only runs the no-op normalize/simpleScale.
|
|
166
|
+
* Verified against the native binary (T8, v2-silent-gaps): output is
|
|
167
|
+
* independent of those attributes, so this is a faithful no-op, not a gap.
|
|
168
|
+
* The single-component path (adjustNodesFull on the real graph,
|
|
169
|
+
* circularinit.c:203) IS ported — see circoLayout.
|
|
167
170
|
*/
|
|
168
171
|
export function adjustNodes(_sg: SubGraph): void {
|
|
169
|
-
//
|
|
172
|
+
// faithful no-op: the derived graph carries no overlap/normalize/scale attrs
|
|
170
173
|
}
|
|
171
174
|
|
|
172
175
|
// ---------------------------------------------------------------------------
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* @see lib/dotgen/dotinit.c:copyClusterInfo
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
+
import { InternalError } from '../../errors.js';
|
|
23
24
|
import type { Graph } from '../../model/graph.js';
|
|
24
25
|
import type { Node } from '../../model/node.js';
|
|
25
26
|
import type { Point, Box } from '../../model/geom.js';
|
|
@@ -253,10 +254,13 @@ export function dotLayoutComponent(sg: Graph, root: Graph): void {
|
|
|
253
254
|
// @see lib/dotgen/dotinit.c:copyClusterInfo (412)
|
|
254
255
|
// ---------------------------------------------------------------------------
|
|
255
256
|
|
|
256
|
-
/**
|
|
257
|
+
/**
|
|
258
|
+
* Map a cluster clone back to its original root cluster. C's `mapClust`.
|
|
259
|
+
* @see lib/pack/ccomps.c:mapClust (assert(op) at ccomps.c:366)
|
|
260
|
+
*/
|
|
257
261
|
function mapClust(clone: Graph, origOf: Map<Graph, Graph>): Graph {
|
|
258
262
|
const orig = origOf.get(clone);
|
|
259
|
-
if (orig === undefined) throw new
|
|
263
|
+
if (orig === undefined) throw new InternalError(`mapClust: no original for cluster ${clone.name}`);
|
|
260
264
|
return orig;
|
|
261
265
|
}
|
|
262
266
|
|
|
@@ -10,7 +10,11 @@ import type { Graph } from '../../model/graph.js';
|
|
|
10
10
|
import type { Node } from '../../model/node.js';
|
|
11
11
|
import type { Edge } from '../../model/edge.js';
|
|
12
12
|
import { virtualNode } from './fastgr.js';
|
|
13
|
-
import { SLACKNODE, LEAFSET } from './rank.js';
|
|
13
|
+
import { SLACKNODE, LEAFSET, scaleClamp } from './rank.js';
|
|
14
|
+
import { agGraphAttr } from '../../model/cgraph-ops.js';
|
|
15
|
+
|
|
16
|
+
/** @see C INT_MAX — default network simplex iteration cap */
|
|
17
|
+
const INT_MAX = 2147483647;
|
|
14
18
|
import { rank } from './ns.js';
|
|
15
19
|
import { markLowclusters } from './cluster.js';
|
|
16
20
|
import {
|
|
@@ -103,8 +107,22 @@ export function connectGraph(g: Graph): void {
|
|
|
103
107
|
// nsiter2 — @see lib/dotgen/position.c:nsiter2
|
|
104
108
|
// ---------------------------------------------------------------------------
|
|
105
109
|
|
|
106
|
-
/**
|
|
107
|
-
|
|
110
|
+
/**
|
|
111
|
+
* C's agget(g, "nslimit") INHERITS the root default, so a graph whose own
|
|
112
|
+
* attrs lack the key falls back to the parse-time snapshot, then the root
|
|
113
|
+
* (the same chain rank1 uses for nslimit1).
|
|
114
|
+
* @see lib/dotgen/position.c:nsiter2
|
|
115
|
+
*/
|
|
116
|
+
export function nsiter2(g: Graph): number {
|
|
117
|
+
const s = g.attrs.get('nslimit')
|
|
118
|
+
?? g.graphDefaultsSnapshot?.get('nslimit')
|
|
119
|
+
?? g.root.attrs.get('nslimit')
|
|
120
|
+
// Set only inside a subgraph: C declared it on the root with default "".
|
|
121
|
+
?? agGraphAttr(g.root, 'nslimit');
|
|
122
|
+
if (s === undefined) return INT_MAX;
|
|
123
|
+
// atof: a non-numeric prefix parses as 0, not NaN.
|
|
124
|
+
return scaleClamp(g.nodes.size, parseFloat(s) || 0);
|
|
125
|
+
}
|
|
108
126
|
|
|
109
127
|
// ---------------------------------------------------------------------------
|
|
110
128
|
// make_leafslots / expand_leaves — @see lib/dotgen/position.c
|
package/src/layout/fdp/derive.ts
CHANGED
|
@@ -22,6 +22,7 @@ import { Edge } from '../../model/edge.js';
|
|
|
22
22
|
import type { Box } from '../../model/geom.js';
|
|
23
23
|
import { doGraphLabel } from '../dot/graph-label.js';
|
|
24
24
|
import { layoutMeasurer } from '../../common/nodeinit.js';
|
|
25
|
+
import { inputscaleDivisor } from '../../common/utils-inputscale.js';
|
|
25
26
|
import {
|
|
26
27
|
type Bport,
|
|
27
28
|
gdata,
|
|
@@ -79,8 +80,7 @@ function copyAttr(g: Graph, dg: Graph, attr: string): void {
|
|
|
79
80
|
|
|
80
81
|
/**
|
|
81
82
|
* Use a cluster's "coords" attribute, if any, for the initial position
|
|
82
|
-
* of its derived node
|
|
83
|
-
* no supported input sets it.
|
|
83
|
+
* of its derived node, divided by PSinputscale when > 0.
|
|
84
84
|
* @see lib/fdpgen/layout.c:chkPos
|
|
85
85
|
*/
|
|
86
86
|
function chkPos(g: Graph, n: Node, infop: LayoutInfo, bbp: Box): void {
|
|
@@ -92,10 +92,11 @@ function chkPos(g: Graph, n: Node, infop: LayoutInfo, bbp: Box): void {
|
|
|
92
92
|
}
|
|
93
93
|
const parsed = scanCoords(p);
|
|
94
94
|
if (parsed === null) return; // C warns "expected four doubles"
|
|
95
|
-
|
|
96
|
-
bbp.ll.
|
|
97
|
-
bbp.
|
|
98
|
-
bbp.ur.
|
|
95
|
+
const div = inputscaleDivisor(infop.rootg); // layout.c:320-324
|
|
96
|
+
bbp.ll.x = parsed.box.ll.x / div;
|
|
97
|
+
bbp.ll.y = parsed.box.ll.y / div;
|
|
98
|
+
bbp.ur.x = parsed.box.ur.x / div;
|
|
99
|
+
bbp.ur.y = parsed.box.ur.y / div;
|
|
99
100
|
dndata(n).pinned = parsed.pinned;
|
|
100
101
|
}
|
|
101
102
|
|
package/src/layout/fdp/index.ts
CHANGED
|
@@ -14,8 +14,10 @@ import type { Graph } from '../../model/graph.js';
|
|
|
14
14
|
import type { LayoutEngine } from '../../gvc/context.js';
|
|
15
15
|
import { setEdgeTypeFromAttr } from '../dot/index.js';
|
|
16
16
|
import {
|
|
17
|
-
EDGETYPE_LINE, EDGETYPE_NONE, splineEdges, injectOraclePositions,
|
|
17
|
+
EDGETYPE_LINE, EDGETYPE_NONE, EDGETYPE_ORTHO, splineEdges, injectOraclePositions,
|
|
18
18
|
} from '../neato/splines.js';
|
|
19
|
+
import { EDGETYPE_COMPOUND } from '../dot/splines.js';
|
|
20
|
+
import { RenderError } from '../../errors.js';
|
|
19
21
|
import { neatoSetAspect } from '../neato/init.js';
|
|
20
22
|
import { placeGraphLabel } from '../dot/position-bbox.js';
|
|
21
23
|
import { gvPostprocess } from '../../common/postproc.js';
|
|
@@ -34,6 +36,10 @@ export { deriveGraph, type LayoutInfo } from './derive.js';
|
|
|
34
36
|
export { fdpInitNodeEdge, fdpCleanup } from './init.js';
|
|
35
37
|
export { fdpInitParams, fdpParms } from './tlayout-parms.js';
|
|
36
38
|
|
|
39
|
+
/** @see lib/fdpgen/layout.c:fdpSplines (agwarningf text) */
|
|
40
|
+
const CLUSTER_EDGE_WARNING =
|
|
41
|
+
'splines and cluster edges not supported - using line segments';
|
|
42
|
+
|
|
37
43
|
/**
|
|
38
44
|
* Graph-level initialization: edge type, gdata, clusters, parameters,
|
|
39
45
|
* nodes and edges. GD_ndim is fixed at 2 (the "dim" attribute's 3D+
|
|
@@ -54,13 +60,47 @@ export function fdpInitGraph(g: Graph): void {
|
|
|
54
60
|
}
|
|
55
61
|
|
|
56
62
|
/**
|
|
57
|
-
* Route edges
|
|
58
|
-
* the
|
|
59
|
-
*
|
|
63
|
+
* Route edges with edge type `et`: splineEdges reads the type from GD_flags,
|
|
64
|
+
* so the C local `et = EDGETYPE_LINE` is applied for the call and restored.
|
|
65
|
+
* @see lib/neatogen/neatosplines.c:spline_edges1
|
|
66
|
+
*/
|
|
67
|
+
function splineEdges1(g: Graph, et: number): void {
|
|
68
|
+
const saved = g.info.flags;
|
|
69
|
+
g.info.flags = (saved & ~0xf) | et;
|
|
70
|
+
try {
|
|
71
|
+
splineEdges(g);
|
|
72
|
+
} finally {
|
|
73
|
+
g.info.flags = saved;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Route edges by the resolved edge type. C's State/Nop globals are not
|
|
79
|
+
* modeled: State < GVSPLINES at the tail holds exactly when the
|
|
80
|
+
* `et > EDGETYPE_ORTHO` block did not route (types <= ORTHO, or the
|
|
81
|
+
* HAS_CLUST_EDGE fallback), and Nop = 2 is only set on the compoundEdges
|
|
82
|
+
* path, which throws here. So the tail is the `else` of the block.
|
|
60
83
|
* @see lib/fdpgen/layout.c:fdpSplines
|
|
61
84
|
*/
|
|
62
85
|
function fdpSplines(g: Graph): void {
|
|
63
|
-
|
|
86
|
+
let et = g.info.flags & 0xf;
|
|
87
|
+
if (et > EDGETYPE_ORTHO) {
|
|
88
|
+
if (et === EDGETYPE_COMPOUND) {
|
|
89
|
+
// C: splineEdges(g, compoundEdges, EDGETYPE_SPLINE) — unported router.
|
|
90
|
+
throw new RenderError(
|
|
91
|
+
'splines=compound: cluster-avoiding edge routing (compoundEdges) is not supported yet',
|
|
92
|
+
'UNSUPPORTED_FEATURE',
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
if (g.info.n_cluster_edges) { // HAS_CLUST_EDGE(g)
|
|
96
|
+
console.warn(CLUSTER_EDGE_WARNING);
|
|
97
|
+
et = EDGETYPE_LINE;
|
|
98
|
+
} else {
|
|
99
|
+
splineEdges1(g, et);
|
|
100
|
+
return; // State == GVSPLINES
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
splineEdges1(g, et); // State < GVSPLINES
|
|
64
104
|
}
|
|
65
105
|
|
|
66
106
|
/**
|
package/src/layout/fdp/init.ts
CHANGED
|
@@ -17,6 +17,7 @@ import type { Graph } from '../../model/graph.js';
|
|
|
17
17
|
import type { Node } from '../../model/node.js';
|
|
18
18
|
import type { Edge } from '../../model/edge.js';
|
|
19
19
|
import { commonInitNodeEdge, lateDouble, layoutMeasurer } from '../../common/nodeinit.js';
|
|
20
|
+
import { inputscaleDivisor } from '../../common/utils-inputscale.js';
|
|
20
21
|
import { initEdgeLabels } from '../../common/edge-label-init.js';
|
|
21
22
|
import type { TextMeasurer } from '../../common/textmeasure.js';
|
|
22
23
|
import { fdpParms } from './tlayout-parms.js';
|
|
@@ -25,24 +26,24 @@ import { processClusterEdges } from './cluster-edges.js';
|
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
28
|
* Read user-supplied positions from the pos attribute (P_SET; P_PIN
|
|
28
|
-
* with a "!" suffix or pin=true)
|
|
29
|
-
* ported — no supported input sets it.
|
|
29
|
+
* with a "!" suffix or pin=true), divided by PSinputscale when > 0.
|
|
30
30
|
* @see lib/fdpgen/fdpinit.c:initialPositions
|
|
31
31
|
*/
|
|
32
32
|
function initialPositions(g: Graph): void {
|
|
33
|
+
const div = inputscaleDivisor(g);
|
|
33
34
|
for (const np of g.nodes.values()) {
|
|
34
35
|
const p = np.attrs.get('pos');
|
|
35
36
|
if (p === undefined || p === '') continue;
|
|
36
|
-
applyPosAttr(np, p);
|
|
37
|
+
applyPosAttr(np, p, div);
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
/** Parse one "x,y[!]" pos value onto np. @see fdpinit.c:47-66 */
|
|
41
|
-
function applyPosAttr(np: Node, p: string): void {
|
|
42
|
+
function applyPosAttr(np: Node, p: string, div: number): void {
|
|
42
43
|
const m = /^(-?[\d.]+),(-?[\d.]+)\s*(!)?/.exec(p);
|
|
43
44
|
if (m === null) return; // C warns "expected two floats"
|
|
44
|
-
np.info.pos![0] = parseFloat(m[1]!);
|
|
45
|
-
np.info.pos![1] = parseFloat(m[2]!);
|
|
45
|
+
np.info.pos![0] = parseFloat(m[1]!) / div;
|
|
46
|
+
np.info.pos![1] = parseFloat(m[2]!) / div;
|
|
46
47
|
const pinAttr = np.attrs.get('pin');
|
|
47
48
|
const pinned = m[3] === '!' ||
|
|
48
49
|
(pinAttr !== undefined && /^(true|yes|1)$/i.test(pinAttr));
|
package/src/layout/fdp/layout.ts
CHANGED
|
@@ -16,6 +16,7 @@ import type { Box, Point } from '../../model/geom.js';
|
|
|
16
16
|
import type { TextlabelT } from '../../common/types.js';
|
|
17
17
|
import { lateInt, lateDouble } from '../../common/nodeinit.js';
|
|
18
18
|
import type { PolygonT } from '../../common/types.js';
|
|
19
|
+
import { agGraphAttr } from '../../model/cgraph-ops.js';
|
|
19
20
|
import { isACluster } from '../dot/rank.js';
|
|
20
21
|
import { BOTTOM_IX, TOP_IX } from '../dot/position-aux.js';
|
|
21
22
|
import {
|
|
@@ -60,7 +61,7 @@ export function initInfo(g: Graph): LayoutInfo {
|
|
|
60
61
|
getPackInfo(g, PackMode.Node, CL_OFFSET / 2, pack);
|
|
61
62
|
return {
|
|
62
63
|
rootg: g,
|
|
63
|
-
hasCoords: g
|
|
64
|
+
hasCoords: agGraphAttr(g, 'coords') !== undefined,
|
|
64
65
|
gid: 0,
|
|
65
66
|
pack,
|
|
66
67
|
};
|
|
@@ -34,12 +34,12 @@ function angleSet(g: Graph): number | null {
|
|
|
34
34
|
if (a === undefined || a === '') return null;
|
|
35
35
|
const ang = parseAngle(a);
|
|
36
36
|
if (ang === null) return null;
|
|
37
|
-
return wrapDegrees(ang) *
|
|
37
|
+
return (wrapDegrees(ang) / 180.0) * Math.PI; // RADIANS(a) = a/180.0*M_PI
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
/** strtod with the mapbool fallback of angleSet. */
|
|
40
|
+
/** strtod (leading whitespace, sign) with the mapbool fallback of angleSet. */
|
|
41
41
|
function parseAngle(a: string): number | null {
|
|
42
|
-
const m =
|
|
42
|
+
const m = /^\s*[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][-+]?\d+)?/.exec(a);
|
|
43
43
|
if (m !== null) return parseFloat(m[0]);
|
|
44
44
|
/* no number */
|
|
45
45
|
return mapbool(a) ? 0.0 : null;
|
|
@@ -55,34 +55,38 @@ function wrapDegrees(ang: number): number {
|
|
|
55
55
|
/**
|
|
56
56
|
* If "normalize" is set, move the first node to the origin, then
|
|
57
57
|
* rotate the layout so the first edge sits at the given angle.
|
|
58
|
+
* Returns 1 if nodes moved, else 0.
|
|
58
59
|
* @see lib/neatogen/adjust.c:normalize (15.0.0)
|
|
59
60
|
*/
|
|
60
|
-
export function normalizeG(g: Graph):
|
|
61
|
+
export function normalizeG(g: Graph): number {
|
|
61
62
|
const phi0 = angleSet(g);
|
|
62
|
-
if (phi0 === null) return;
|
|
63
|
+
if (phi0 === null) return 0;
|
|
63
64
|
|
|
64
65
|
const nodes = [...g.nodes.values()];
|
|
65
66
|
const first = nodes[0];
|
|
66
|
-
if (first === undefined) return;
|
|
67
|
+
if (first === undefined) return 0;
|
|
67
68
|
const px = first.info.pos![0]!;
|
|
68
69
|
const py = first.info.pos![1]!;
|
|
69
70
|
for (const v of nodes) {
|
|
70
71
|
v.info.pos![0]! -= px;
|
|
71
72
|
v.info.pos![1]! -= py;
|
|
72
73
|
}
|
|
74
|
+
const ret = px || py ? 1 : 0;
|
|
73
75
|
|
|
74
76
|
let e = null;
|
|
75
77
|
for (const v of nodes) {
|
|
76
78
|
const out = v.outEdges(g);
|
|
77
79
|
if (out.length > 0) { e = out[0]!; break; }
|
|
78
80
|
}
|
|
79
|
-
if (e === null) return;
|
|
81
|
+
if (e === null) return ret;
|
|
80
82
|
|
|
81
83
|
/* rotation necessary; pos => ccw */
|
|
82
84
|
const phi = phi0 - Math.atan2(
|
|
83
85
|
e.head.info.pos![1]! - e.tail.info.pos![1]!,
|
|
84
86
|
e.head.info.pos![0]! - e.tail.info.pos![0]!);
|
|
85
|
-
if (phi)
|
|
87
|
+
if (!phi) return ret;
|
|
88
|
+
rotateAll(nodes, e.tail, phi);
|
|
89
|
+
return 1;
|
|
86
90
|
}
|
|
87
91
|
|
|
88
92
|
/** Rotate all positions by phi about the orig node. */
|
package/src/layout/fdp/ports.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* (15.0.0)
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { InternalError } from '../../errors.js';
|
|
14
15
|
import type { Graph } from '../../model/graph.js';
|
|
15
16
|
import type { Node } from '../../model/node.js';
|
|
16
17
|
import type { Edge } from '../../model/edge.js';
|
|
@@ -78,7 +79,7 @@ export function getEdgeList(n: Node, g: Graph): Erec[] {
|
|
|
78
79
|
erecs.push({ e, alpha: Math.atan2(dy, dx), dist2: dx * dx + dy * dy });
|
|
79
80
|
}
|
|
80
81
|
if (erecs.length !== deg) {
|
|
81
|
-
throw new
|
|
82
|
+
throw new InternalError(`fdp getEdgeList: ${erecs.length} edges vs deg ${deg}`);
|
|
82
83
|
}
|
|
83
84
|
erecs.sort(ecmp);
|
|
84
85
|
|
|
@@ -151,7 +152,7 @@ export function expandCluster(n: Node, cg: Graph): Graph {
|
|
|
151
152
|
idx = genPorts(n, es[k]!, pp, idx, bnd);
|
|
152
153
|
}
|
|
153
154
|
if (idx !== sz) {
|
|
154
|
-
throw new
|
|
155
|
+
throw new InternalError(`fdp expandCluster: ${idx} ports vs wdeg ${sz}`);
|
|
155
156
|
}
|
|
156
157
|
|
|
157
158
|
gdata(sg).ports = pp;
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* @see lib/fdpgen/xlayout.c (15.0.0)
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
+
import { RenderError } from '../../errors.js';
|
|
20
21
|
import type { Graph } from '../../model/graph.js';
|
|
21
22
|
import type { Node } from '../../model/node.js';
|
|
22
23
|
import { sepFactor, type ExpandT } from '../neato/sep-factor.js';
|
|
@@ -31,6 +32,7 @@ import {
|
|
|
31
32
|
} from './fdp-model.js';
|
|
32
33
|
import { coincidentDelta } from './tlayout.js';
|
|
33
34
|
import { normalizeG } from './normalize.js';
|
|
35
|
+
import { simpleScale } from '../neato/simple-scale.js';
|
|
34
36
|
|
|
35
37
|
/** @see lib/fdpgen/xlayout.c:DFLT_overlap */
|
|
36
38
|
const DFLT_OVERLAP = '9:prism';
|
|
@@ -313,14 +315,14 @@ export function fdpXLayout(g: Graph, xpms: XParams): void {
|
|
|
313
315
|
* scale family (scale/scalexy/compress) → scAdjust;
|
|
314
316
|
* AM_NONE ('' / 'true') and unported modes (voronoi) → no-op.
|
|
315
317
|
* normalize reads "normalize" and simpleScale reads "scale"; deriveGraph
|
|
316
|
-
* copies only overlap/sep/K, so both are faithfully dead here
|
|
317
|
-
*
|
|
318
|
-
* follows the project's decision to leave it unported.
|
|
318
|
+
* copies only overlap/sep/K, so both are faithfully dead here unless set on
|
|
319
|
+
* the derived chain (native fdp ignores both attributes).
|
|
319
320
|
* @see lib/neatogen/adjust.c:removeOverlapAs / removeOverlapWith / getAdjustMode
|
|
320
321
|
*/
|
|
321
322
|
function removeOverlapAs(g: Graph, flag: string): void {
|
|
322
323
|
if (g.nodes.size < 2) return; // removeOverlapWith: <2 nodes short-circuits
|
|
323
|
-
normalizeG(g); // removeOverlapWith runs normalize
|
|
324
|
+
normalizeG(g); // removeOverlapWith runs normalize + simpleScale first
|
|
325
|
+
simpleScale(g);
|
|
324
326
|
const ntry = overlapPrismTries(flag);
|
|
325
327
|
if (ntry !== null) {
|
|
326
328
|
fdpAdjust(g, ntry); // AM_PRISM
|
|
@@ -333,9 +335,10 @@ function removeOverlapAs(g: Graph, flag: string): void {
|
|
|
333
335
|
// Genuinely unported adjust algorithms: throw rather than silently leave
|
|
334
336
|
// overlaps (no supported corpus input reaches these).
|
|
335
337
|
if (UNPORTED_MODES.has(mode)) {
|
|
336
|
-
throw new
|
|
338
|
+
throw new RenderError(
|
|
337
339
|
`fdp: removeOverlapAs mode "${mode}" reached for graph "${g.name}" — ` +
|
|
338
340
|
'that adjust algorithm (voronoi/oscale/vpsc/ortho/ipsep) is not ported',
|
|
341
|
+
'UNSUPPORTED_FEATURE',
|
|
339
342
|
);
|
|
340
343
|
}
|
|
341
344
|
// AM_NONE ('', 'true', any boolean-true) → no overlap removal.
|