@knowvah/dot-engine 2.7.0 → 2.8.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 (122) hide show
  1. package/dist/api.js.map +2 -2
  2. package/dist/common/agerr.d.ts +8 -1
  3. package/dist/common/agerr.d.ts.map +1 -1
  4. package/dist/common/attr-get.d.ts +30 -0
  5. package/dist/common/attr-get.d.ts.map +1 -0
  6. package/dist/common/fma.d.ts.map +1 -1
  7. package/dist/common/htmltable-emit-fill.d.ts.map +1 -1
  8. package/dist/common/htmltable-label.d.ts +3 -0
  9. package/dist/common/htmltable-label.d.ts.map +1 -1
  10. package/dist/common/htmltable-lex-attvalue.d.ts.map +1 -1
  11. package/dist/common/htmltable.d.ts +5 -0
  12. package/dist/common/htmltable.d.ts.map +1 -1
  13. package/dist/common/layers.d.ts +12 -0
  14. package/dist/common/layers.d.ts.map +1 -1
  15. package/dist/common/layout-state.d.ts +8 -0
  16. package/dist/common/layout-state.d.ts.map +1 -0
  17. package/dist/common/make-label.d.ts.map +1 -1
  18. package/dist/common/multicolor.d.ts +6 -3
  19. package/dist/common/multicolor.d.ts.map +1 -1
  20. package/dist/common/nodeinit.d.ts.map +1 -1
  21. package/dist/common/poly-init.d.ts +13 -12
  22. package/dist/common/poly-init.d.ts.map +1 -1
  23. package/dist/common/poly-sizing.d.ts +1 -0
  24. package/dist/common/poly-sizing.d.ts.map +1 -1
  25. package/dist/common/postproc.d.ts.map +1 -1
  26. package/dist/common/style-resolve.d.ts +11 -9
  27. package/dist/common/style-resolve.d.ts.map +1 -1
  28. package/dist/gvc/device-cluster.d.ts +5 -6
  29. package/dist/gvc/device-cluster.d.ts.map +1 -1
  30. package/dist/gvc/device.d.ts.map +1 -1
  31. package/dist/gvc/first-layer.d.ts +21 -5
  32. package/dist/gvc/first-layer.d.ts.map +1 -1
  33. package/dist/gvc/gvrender-set-style.d.ts.map +1 -1
  34. package/dist/gvc/usershape.d.ts +20 -0
  35. package/dist/gvc/usershape.d.ts.map +1 -1
  36. package/dist/index.js +512 -286
  37. package/dist/index.js.map +4 -4
  38. package/dist/layout/circo/index.d.ts.map +1 -1
  39. package/dist/layout/dot/graph-label.d.ts.map +1 -1
  40. package/dist/layout/dot/init.d.ts +10 -0
  41. package/dist/layout/dot/init.d.ts.map +1 -1
  42. package/dist/layout/dot/splines.d.ts.map +1 -1
  43. package/dist/layout/fdp/init.d.ts.map +1 -1
  44. package/dist/layout/neato/conjgrad-mkernel.d.ts.map +1 -1
  45. package/dist/layout/neato/constrained-majorization.d.ts.map +1 -1
  46. package/dist/layout/neato/edge-len.d.ts +6 -2
  47. package/dist/layout/neato/edge-len.d.ts.map +1 -1
  48. package/dist/layout/neato/init.d.ts.map +1 -1
  49. package/dist/layout/neato/kk.d.ts.map +1 -1
  50. package/dist/layout/neato/make-graph-data.d.ts.map +1 -1
  51. package/dist/layout/neato/matrix-ops.d.ts +0 -6
  52. package/dist/layout/neato/matrix-ops.d.ts.map +1 -1
  53. package/dist/layout/neato/smart-ini-x.d.ts.map +1 -1
  54. package/dist/layout/neato/splines.d.ts.map +1 -1
  55. package/dist/layout/neato/start.d.ts.map +1 -1
  56. package/dist/layout/neato/stress-kernel.d.ts +2 -5
  57. package/dist/layout/neato/stress-kernel.d.ts.map +1 -1
  58. package/dist/layout/neato/stress.d.ts +6 -0
  59. package/dist/layout/neato/stress.d.ts.map +1 -1
  60. package/dist/layout/osage/index.d.ts.map +1 -1
  61. package/dist/layout/patchwork/index.d.ts.map +1 -1
  62. package/dist/layout/sfdp/init.d.ts.map +1 -1
  63. package/dist/layout/twopi/init.d.ts +12 -6
  64. package/dist/layout/twopi/init.d.ts.map +1 -1
  65. package/dist/model/cgraph-ops.d.ts +9 -0
  66. package/dist/model/cgraph-ops.d.ts.map +1 -1
  67. package/dist/render/color-resolve.d.ts +7 -0
  68. package/dist/render/color-resolve.d.ts.map +1 -1
  69. package/dist/render/json.d.ts +17 -2
  70. package/dist/render/json.d.ts.map +1 -1
  71. package/dist/render/map.d.ts +3 -5
  72. package/dist/render/map.d.ts.map +1 -1
  73. package/dist/render.js +518 -292
  74. package/dist/render.js.map +4 -4
  75. package/package.json +1 -1
  76. package/src/common/agerr.ts +69 -8
  77. package/src/common/attr-get.ts +46 -0
  78. package/src/common/fma.ts +11 -1
  79. package/src/common/htmltable-emit-fill.ts +11 -7
  80. package/src/common/htmltable-label.ts +6 -3
  81. package/src/common/htmltable-lex-attvalue.ts +17 -1
  82. package/src/common/htmltable.ts +10 -5
  83. package/src/common/layers.ts +31 -2
  84. package/src/common/layout-state.ts +35 -0
  85. package/src/common/make-label.ts +20 -4
  86. package/src/common/multicolor.ts +12 -6
  87. package/src/common/nodeinit.ts +2 -1
  88. package/src/common/poly-init.ts +22 -19
  89. package/src/common/poly-sizing.ts +4 -4
  90. package/src/common/postproc.ts +4 -1
  91. package/src/common/style-resolve.ts +118 -50
  92. package/src/gvc/device-cluster.ts +31 -39
  93. package/src/gvc/device.ts +55 -43
  94. package/src/gvc/first-layer.ts +60 -11
  95. package/src/gvc/gvrender-set-style.ts +1 -6
  96. package/src/gvc/usershape.ts +40 -0
  97. package/src/layout/circo/index.ts +4 -0
  98. package/src/layout/dot/graph-label.ts +1 -15
  99. package/src/layout/dot/init.ts +32 -0
  100. package/src/layout/dot/splines.ts +3 -0
  101. package/src/layout/fdp/init.ts +17 -0
  102. package/src/layout/neato/conjgrad-mkernel.ts +2 -3
  103. package/src/layout/neato/constrained-majorization.ts +2 -1
  104. package/src/layout/neato/edge-len.ts +39 -7
  105. package/src/layout/neato/init.ts +8 -3
  106. package/src/layout/neato/kk.ts +2 -1
  107. package/src/layout/neato/make-graph-data.ts +1 -2
  108. package/src/layout/neato/matrix-ops.ts +1 -11
  109. package/src/layout/neato/smart-ini-x.ts +1 -1
  110. package/src/layout/neato/splines.ts +3 -1
  111. package/src/layout/neato/start.ts +3 -2
  112. package/src/layout/neato/stress-kernel.ts +45 -86
  113. package/src/layout/neato/stress.ts +19 -0
  114. package/src/layout/osage/index.ts +8 -1
  115. package/src/layout/patchwork/index.ts +4 -0
  116. package/src/layout/sfdp/init.ts +3 -0
  117. package/src/layout/twopi/init.ts +21 -9
  118. package/src/layout/twopi/pipeline.ts +3 -3
  119. package/src/model/cgraph-ops.ts +19 -0
  120. package/src/render/color-resolve.ts +17 -2
  121. package/src/render/json.ts +9 -10
  122. package/src/render/map.ts +5 -6
@@ -13,6 +13,7 @@
13
13
 
14
14
  import { agwarningf } from '../common/agerr.js';
15
15
  import { parseSegsSilent } from '../common/multicolor.js';
16
+ import { styleName } from '../common/style-resolve.js';
16
17
  import type { PolygonT } from '../common/types.js';
17
18
  import type { Edge } from '../model/edge.js';
18
19
 
@@ -29,12 +30,6 @@ const BOX_SIDES = 4;
29
30
  /** polygon_t.sides bound of shapes.c:isEllipse. @see lib/common/shapes.c:isEllipse */
30
31
  const ELLIPSE_MAX_SIDES = 2;
31
32
 
32
- /** The name C compares: parse_style stores a token's `(args)` after its NUL. */
33
- function styleName(token: string): string {
34
- const paren = token.indexOf('(');
35
- return paren < 0 ? token : token.slice(0, paren);
36
- }
37
-
38
33
  /**
39
34
  * Warn for every token the render engine cannot map, on every call.
40
35
  * @see lib/gvc/gvrender.c:480 gvrender_set_style
@@ -63,3 +63,43 @@ export function imageIsSupplied(name: string, sizer?: ImageSizer, resolver?: Ima
63
63
  export function imageOpenWarning(name: string): string {
64
64
  return `${STRERROR_ENOENT} while opening ${name}`;
65
65
  }
66
+
67
+ /** @see lib/gvc/gvusershape.c:DEFAULT_DPI */
68
+ export const DEFAULT_DPI = 96;
69
+ /** @see lib/common/geom.h:POINTS_PER_INCH */
70
+ const POINTS_PER_INCH = 72;
71
+
72
+ /**
73
+ * The graph's `dpi` as `gvusershape_size` reads it: GD_drawing->dpi is
74
+ * late_double(dpi, resolution, 0) (input.c:705), and a value below 1 means
75
+ * the 96 default.
76
+ * @see lib/common/input.c:graph_init (dpi), lib/gvc/gvusershape.c:gvusershape_size
77
+ */
78
+ export function usershapeDpi(attrs: ReadonlyMap<string, string>): number {
79
+ const lateDouble = (s: string | undefined, dflt: number): number => {
80
+ if (s === undefined || s === '') return dflt;
81
+ const v = Number(s);
82
+ return Number.isNaN(v) || s.trim() === '' ? dflt : Math.max(v, 0);
83
+ };
84
+ const dpi = lateDouble(attrs.get('dpi'), lateDouble(attrs.get('resolution'), 0));
85
+ return dpi >= 1 ? dpi : DEFAULT_DPI;
86
+ }
87
+
88
+ /**
89
+ * Image size in points: `gvusershape_size`. The caller's sizer reports pixels;
90
+ * the result is `(int)(px * 72 / dpi)` (the file's own dpi is unknown to a
91
+ * sizer, so `us->dpi` is 0 as for most formats). Null stands for C's (-1,-1):
92
+ * empty name or an image nobody supplies.
93
+ * @see lib/gvc/gvusershape.c:gvusershape_size, gvusershape_size_dpi
94
+ */
95
+ export function usershapeSizePts(
96
+ name: string | undefined, dpi: number, sizer?: ImageSizer,
97
+ ): { x: number; y: number } | null {
98
+ if (name === undefined || name === '') return null;
99
+ const px = sizer !== undefined ? sizer(name) : findImageSize(name);
100
+ if (px === null) return null;
101
+ return {
102
+ x: Math.trunc((px.w * POINTS_PER_INCH) / dpi),
103
+ y: Math.trunc((px.h * POINTS_PER_INCH) / dpi),
104
+ };
105
+ }
@@ -18,6 +18,7 @@
18
18
  */
19
19
 
20
20
  import type { Graph } from '../../model/graph.js';
21
+ import { gvCleanupEdgeLabels, gvCleanupNodeLabels } from '../dot/init.js';
21
22
  import type { LayoutEngine } from '../../gvc/context.js';
22
23
  import { circoInitGraph, freeNData } from './init.js';
23
24
  import { circoLayout } from './circular.js';
@@ -72,6 +73,9 @@ export function circoLayoutFull(g: Graph): void {
72
73
 
73
74
  /** @see lib/circogen/circularinit.c:circo_cleanup */
74
75
  export function circoCleanup(g: Graph): void {
76
+ if (g.nodes.size === 0) return; // C: g is empty
77
+ for (const e of g.edges) gvCleanupEdgeLabels(e); // gv_cleanup_edge: per-node agfstout, all edges of root g
78
+ for (const n of g.nodes.values()) gvCleanupNodeLabels(n); // gv_cleanup_node
75
79
  // Derived graph was stored in g.info.alg during circoLayout; clear it.
76
80
  g.info.alg = undefined;
77
81
  g.info.neato_nlist = undefined;
@@ -9,6 +9,7 @@
9
9
  import type { Graph } from '../../model/graph.js';
10
10
  import type { Point } from '../../model/geom.js';
11
11
  import type { TextMeasurer } from '../../common/textmeasure.js';
12
+ import { graphAttrInherited } from '../../common/attr-get.js';
12
13
  import { makeAnyLabel, DEFAULT_FONTSIZE, DEFAULT_FONTNAME, DEFAULT_COLOR } from '../../common/make-label.js';
13
14
  import { isHtmlValue, htmlValueContent } from '../../common/html-string.js';
14
15
  import { BOTTOM_IX, TOP_IX, RIGHT_IX, LEFT_IX } from './position-aux.js';
@@ -45,21 +46,6 @@ function readLabelPos(sg: Graph): number {
45
46
  return pos;
46
47
  }
47
48
 
48
- /**
49
- * Read a graph attribute with subgraph→root inheritance, mirroring C's
50
- * agxget (late_nnstring(sg, agfindgraphattr(sg, key), ...) in do_graph_label):
51
- * a cluster inherits `fontname`/`fontsize`/`fontcolor` set on an ancestor
52
- * (e.g. root `graph[fontname=Arial]`) when it sets none of its own.
53
- * @see lib/common/input.c:do_graph_label
54
- */
55
- function graphAttrInherited(sg: Graph, key: string): string | undefined {
56
- // Own value wins; otherwise the ancestor defaults SNAPSHOTTED when this
57
- // subgraph opened (not a live parent walk) — so an ancestor attribute set
58
- // later in statement order does not retroactively apply (2184).
59
- // @see lib/cgraph/graph.c:agsubg (parse-time defval copy)
60
- return sg.attrs.get(key) ?? sg.graphDefaultsSnapshot?.get(key);
61
- }
62
-
63
49
  /**
64
50
  * Graph/cluster label font, read through graphAttrInherited.
65
51
  * @internal also used by src/async/collect.ts to request the same face.
@@ -275,6 +275,35 @@ export function removeFill(g: Graph): void {
275
275
  // dotCleanup
276
276
  // ---------------------------------------------------------------------------
277
277
 
278
+ /**
279
+ * The label frees shared by every engine's per-edge cleanup.
280
+ * @see lib/common/utils.c:gv_cleanup_edge
281
+ */
282
+ export function gvCleanupEdgeLabels(e: Edge): void {
283
+ e.info.label = undefined;
284
+ e.info.xlabel = undefined;
285
+ e.info.head_label = undefined;
286
+ e.info.tail_label = undefined;
287
+ }
288
+
289
+ /**
290
+ * The label frees shared by every non-dot engine's per-node cleanup.
291
+ * @see lib/common/utils.c:gv_cleanup_node
292
+ */
293
+ export function gvCleanupNodeLabels(n: Node): void {
294
+ n.info.label = undefined;
295
+ n.info.xlabel = undefined;
296
+ }
297
+
298
+ /**
299
+ * Frees non-root subgraph labels, recursing into every subgraph first.
300
+ * @see lib/dotgen/dotinit.c:dot_cleanup_graph
301
+ */
302
+ function dotCleanupGraph(g: Graph): void {
303
+ for (const subg of g.subgraphs.values()) dotCleanupGraph(subg);
304
+ if (g !== g.root) g.info.label = undefined;
305
+ }
306
+
278
307
  /**
279
308
  * Clears all edge lists and linked-list pointers from nodes in the fast-graph,
280
309
  * releasing layout state without destroying the underlying graph structure.
@@ -284,6 +313,9 @@ export function removeFill(g: Graph): void {
284
313
  * @see lib/dotgen/dotinit.c:dot_cleanup
285
314
  */
286
315
  export function dotCleanup(g: Graph): void {
316
+ for (const e of g.edges) gvCleanupEdgeLabels(e); // gv_cleanup_edge (all out-edges)
317
+ for (const n of g.nodes.values()) gvCleanupNodeLabels(n); // dot_cleanup_node
318
+ dotCleanupGraph(g);
287
319
  let n: Node | undefined = g.info.nlist;
288
320
  while (n !== undefined) {
289
321
  const next: Node | undefined = n.info.next;
@@ -27,6 +27,7 @@ import { routeEdgeGroups } from './splines-groups.js';
27
27
  import { placePortLabels, placeRegularEdgeLabels, setEdgeLabelPos } from './splines-label.js';
28
28
  import { nodesInSeq } from './decomp.js';
29
29
  import { agerrLineOn } from '../../common/agerr.js';
30
+ import { setState, GVSPLINES } from '../../common/layout-state.js';
30
31
 
31
32
  // ---------------------------------------------------------------------------
32
33
  // Edge-type flag constants
@@ -462,6 +463,7 @@ function orthoDispatch(g: Graph): number {
462
463
  // are placed via place_portlabel for ortho too. Skipping this left the
463
464
  // labels unset, so the xlabels pass placed them instead (144_ortho).
464
465
  placePortLabels(g);
466
+ setState(GVSPLINES); // @see dotsplines.c:477 (reached via goto finish)
465
467
  g.info.edgeLabelsDone = true;
466
468
  return 0;
467
469
  }
@@ -487,6 +489,7 @@ function finalizeSplines(g: Graph, et: number, normalize: boolean): void {
487
489
  if (normalize) edgeNormalize(g);
488
490
  if (et !== EDGETYPE_CURVED) routeDotEdges(g);
489
491
  placePortLabels(g);
492
+ setState(GVSPLINES); // @see dotsplines.c:477
490
493
  g.info.edgeLabelsDone = true; // @see dotsplines.c:471 (EdgeLabelsDone = 1)
491
494
  }
492
495
 
@@ -14,6 +14,7 @@
14
14
  */
15
15
 
16
16
  import type { Graph } from '../../model/graph.js';
17
+ import { gvCleanupEdgeLabels, gvCleanupNodeLabels } from '../dot/init.js';
17
18
  import type { Node } from '../../model/node.js';
18
19
  import type { Edge } from '../../model/edge.js';
19
20
  import { commonInitNodeEdge, lateDouble, layoutMeasurer } from '../../common/nodeinit.js';
@@ -99,12 +100,28 @@ export function fdpInitNodeEdge(g: Graph): void {
99
100
  initialPositions(g);
100
101
  }
101
102
 
103
+ /**
104
+ * Frees each cluster label, recursively.
105
+ * @see lib/fdpgen/fdpinit.c:cleanup_subgs
106
+ */
107
+ function fdpCleanupSubgs(g: Graph): void {
108
+ const nClust = g.info.n_cluster ?? 0;
109
+ for (let i = 1; i <= nClust; i++) {
110
+ const subg = g.info.clust![i - 1];
111
+ subg.info.label = undefined;
112
+ fdpCleanupSubgs(subg);
113
+ }
114
+ }
115
+
102
116
  /**
103
117
  * Release fdp layout state (gv_cleanup analogue; the render pipeline
104
118
  * runs before cleanup per the M2 ordering fix).
105
119
  * @see lib/fdpgen/fdpinit.c:fdp_cleanup
106
120
  */
107
121
  export function fdpCleanup(g: Graph): void {
122
+ for (const e of g.edges) gvCleanupEdgeLabels(e); // gv_cleanup_edge: per-node agfstout, all edges of root g
123
+ for (const n of g.nodes.values()) gvCleanupNodeLabels(n); // gv_cleanup_node
124
+ fdpCleanupSubgs(g);
108
125
  for (const n of g.nodes.values()) {
109
126
  n.info.alg = undefined;
110
127
  n.info.pos = undefined;
@@ -6,9 +6,8 @@
6
6
  * C statement `a += b * c` on floats is one `fmadd`/`fmla` (clang's default
7
7
  * contraction), and a reduction the vectoriser took in blocks of 16 rounds
8
8
  * the products of those leading elements before its in-order adds (strict FP
9
- * forbids reassociation) while the scalar remainder is fused. `stress-kernel.ts`
10
- * keeps the unfused rounding of these loops; the constrained-majorization driver
11
- * needs the oracle's bits.
9
+ * forbids reassociation) while the scalar remainder is fused. These kernels run
10
+ * `stress_majorization_kD_mkernel` and the constrained-majorization driver.
12
11
  *
13
12
  * @see lib/neatogen/conjgrad.c:conjugate_gradient_mkernel
14
13
  * @see lib/neatogen/matrix_ops.c:right_mult_with_vector_ff
@@ -14,6 +14,7 @@
14
14
  * @see lib/neatogen/constrained_majorization.c
15
15
  */
16
16
 
17
+ import { fma } from '../../common/fma.js';
17
18
  import { RenderError } from '../../errors.js';
18
19
  import type { Node } from '../../model/node.js';
19
20
  import { conjugateGradientMkernelFused, rightMultWithVectorFFFused } from './conjgrad-mkernel.js';
@@ -21,7 +22,7 @@ import { computeHierarchy, type Hierarchy } from './compute-hierarchy.js';
21
22
  import type { VtxData } from './dijkstra.js';
22
23
  import { MODEL_CIRCUIT, MODEL_MDS, MODEL_SUBSET } from './init.js';
23
24
  import {
24
- fma, invertSqrtVec, invertVec, orthog1, sqrtVecF, squareVec, vectorsInnerProductF,
25
+ invertSqrtVec, invertVec, orthog1, sqrtVecF, squareVec, vectorsInnerProductF,
25
26
  } from './matrix-ops.js';
26
27
  import type { DirectedVtxData } from './opt-arrangement.js';
27
28
  import { computeYCoords } from './opt-arrangement.js';
@@ -8,12 +8,33 @@
8
8
  */
9
9
 
10
10
  import type { Graph } from '../../model/graph.js';
11
+ import { agNameOf } from '../../model/cgraph-ops.js';
11
12
  import type { Edge } from '../../model/edge.js';
12
13
  import { agwarningf } from '../../common/agerr.js';
14
+ import { buildOutEdgeIndex } from '../../model/node.js';
13
15
 
14
16
  /** `dfltlen` passed by scan_graph_mode. @see lib/neatogen/stuff.c:scan_graph_mode */
15
17
  export const DEFAULT_EDGE_LEN = 1.0;
16
18
 
19
+ /** sscanf("%lf") number prefix: decimal, hex, inf/infinity, nan (case-insensitive). */
20
+ const DOUBLE_PREFIX =
21
+ /^\s*([+-]?)(0x(?:[\da-f]+\.?[\da-f]*|\.[\da-f]+)(?:p[+-]?\d+)?|(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?|inf(?:inity)?|nan(?:\([\w]*\))?)/i;
22
+
23
+ /** `sscanf(s, "%lf", &v)`: the parsed value (NaN for "nan"), or null when nothing scans. */
24
+ function scanDouble(s: string): number | null {
25
+ const m = DOUBLE_PREFIX.exec(s);
26
+ if (m === null) return null;
27
+ const body = m[2]!.toLowerCase();
28
+ const sign = m[1] === '-' ? -1 : 1;
29
+ if (body.startsWith('nan')) return Number.NaN;
30
+ if (body.startsWith('inf')) return sign * Infinity;
31
+ if (!body.startsWith('0x')) return sign * parseFloat(body);
32
+ const [mant, exp] = body.slice(2).split('p');
33
+ const [ip = '', fp = ''] = mant!.split('.');
34
+ const digits = parseInt(ip + fp || '0', 16);
35
+ return sign * digits * 2 ** (Number(exp ?? 0) - 4 * fp.length);
36
+ }
37
+
17
38
  /**
18
39
  * The edge's `len`, or the default when it is absent or empty; a value that
19
40
  * does not parse as a positive number warns and falls back to the default.
@@ -22,16 +43,27 @@ export const DEFAULT_EDGE_LEN = 1.0;
22
43
  export function lenattr(ep: Edge, g: Graph): number {
23
44
  const s = ep.attrs.get('len');
24
45
  if (s === undefined || s === '') return DEFAULT_EDGE_LEN;
25
- const val = /^\s*[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?/.exec(s);
26
- const len = val === null ? Number.NaN : parseFloat(val[0]);
27
- if (Number.isNaN(len) || len < 0 || len === 0) {
28
- agwarningf(`bad edge len "${s}" in ${g.name} - setting to ${DEFAULT_EDGE_LEN.toFixed(2)}`);
46
+ const len = scanDouble(s);
47
+ if (len === null || len < 0 || len === 0) {
48
+ agwarningf(`bad edge len "${s}" in ${agNameOf(g)} - setting to ${DEFAULT_EDGE_LEN.toFixed(2)}`);
29
49
  return DEFAULT_EDGE_LEN;
30
50
  }
31
51
  return len;
32
52
  }
33
53
 
34
- /** Store every edge's length in `info.dist` (ED_dist). @see lib/neatogen/stuff.c:setEdgeLen */
35
- export function setEdgeLen(g: Graph): void {
36
- for (const ep of g.edges) ep.info.dist = lenattr(ep, g);
54
+ /**
55
+ * Store every edge's length in `info.dist` (ED_dist), visiting nodes in order and each
56
+ * node's out-edges in `agfstout` order as scan_graph_mode does; returns the total length.
57
+ * @see lib/neatogen/stuff.c:setEdgeLen
58
+ */
59
+ export function setEdgeLen(g: Graph): number {
60
+ const out = buildOutEdgeIndex(g);
61
+ let total = 0;
62
+ for (const np of g.nodes.values()) {
63
+ for (const ep of out.get(np) ?? []) {
64
+ ep.info.dist = lenattr(ep, g);
65
+ total += ep.info.dist;
66
+ }
67
+ }
68
+ return total;
37
69
  }
@@ -11,12 +11,14 @@
11
11
 
12
12
  import type { ShapeDesc } from '../../common/types.js';
13
13
  import type { Graph } from '../../model/graph.js';
14
+ import { gvCleanupEdgeLabels, gvCleanupNodeLabels } from '../dot/init.js';
14
15
  import type { Node } from '../../model/node.js';
15
16
  import type { Edge } from '../../model/edge.js';
16
17
  import type { VtxData } from './dijkstra.js';
17
18
  import {
18
19
  DFLT_ITERATIONS,
19
20
  OPT_SMART_INIT as STRESS_OPT_SMART_INIT,
21
+ checkExp,
20
22
  MODEL_MDS as STRESS_MODEL_MDS,
21
23
  MODEL_CIRCUIT as STRESS_MODEL_CIRCUIT,
22
24
  MODEL_SUBSET as STRESS_MODEL_SUBSET,
@@ -26,6 +28,7 @@ import { kkNeato } from './kk.js';
26
28
  import {
27
29
  checkStart, assertSupported, parseStart, INIT_RANDOM, INIT_REGULAR, INIT_SELF,
28
30
  } from './start.js';
31
+ import { setEdgeLen } from './edge-len.js';
29
32
  import { makeGraphDataC, graphHasLen } from './make-graph-data.js';
30
33
  import type { DirectedVtxData } from './opt-arrangement.js';
31
34
  import { stressMajorizationWithHierarchy } from './constrained-majorization.js';
@@ -37,7 +40,6 @@ import {
37
40
  computeApspPacked,
38
41
  computeWeightedApspPacked,
39
42
  stressMajorizationKDMkernel,
40
- OPT_EXP_FLAG,
41
43
  } from './stress-kernel.js';
42
44
  import { lateDouble } from '../../common/nodeinit.js';
43
45
  import { inputscaleDivisor } from '../../common/utils-inputscale.js';
@@ -373,18 +375,19 @@ function solveStress(
373
375
  const weighted = haveLen && mode !== MODE_IPSEP;
374
376
  const Dij = weighted ? computeWeightedApspPacked(vtx, n) : computeApspPacked(vtx, n);
375
377
  return stressMajorizationKDMkernel(Dij, n, dCoords, nodeList, {
376
- dim: DFLT_DIM, opts, maxi, epsilon,
378
+ dim: DFLT_DIM, opts, maxi, epsilon, cola: mode === MODE_IPSEP,
377
379
  });
378
380
  }
379
381
 
380
382
  /** Run stress majorization and write positions back. @see lib/neatogen/neatoinit.c:majorization */
381
383
  export function runMajorization(g: Graph, mode: number, model: number): void {
382
384
  const nodeList = assignNodeIds(g);
385
+ setEdgeLen(g); // scan_graph_mode else-branch: ED_dist for major/hier/ipsep
383
386
  const n = nodeList.length;
384
387
  const haveLen = graphHasLen(g);
385
388
  // C: srand48(seed) immediately before initLayout draws; hier starts from self
386
389
  const init = checkStart(g, n, mode === MODE_HIER ? INIT_SELF : INIT_RANDOM);
387
- const opts = init === INIT_SELF ? OPT_EXP_FLAG | STRESS_OPT_SMART_INIT : OPT_EXP_FLAG;
390
+ const opts = checkExp(g) | (init === INIT_SELF ? STRESS_OPT_SMART_INIT : 0);
388
391
  const haveDir = mode === MODE_HIER || mode === MODE_IPSEP;
389
392
  const vtx = makeGraphDataC(g, nodeList, haveLen, haveDir, mode);
390
393
  const dCoords = [new Float64Array(n), new Float64Array(n)];
@@ -489,6 +492,8 @@ export function neatoSetAspect(g: Graph): void {
489
492
  * @see lib/neatogen/neatoinit.c:neato_cleanup
490
493
  */
491
494
  export function neatoCleanup(g: Graph): void {
495
+ for (const e of g.edges) gvCleanupEdgeLabels(e); // gv_cleanup_edge: per-node agfstout, all edges of root g
496
+ for (const n of g.nodes.values()) gvCleanupNodeLabels(n); // gv_cleanup_node
492
497
  for (const [, n] of g.nodes) {
493
498
  n.info.pos = undefined;
494
499
  n.info.pinned = undefined;
@@ -17,6 +17,7 @@
17
17
  */
18
18
 
19
19
  import type { Graph } from '../../model/graph.js';
20
+ import { agNameOf } from '../../model/cgraph-ops.js';
20
21
  import type { Node } from '../../model/node.js';
21
22
  import type { Edge } from '../../model/edge.js';
22
23
  import { buildOutEdgeIndex } from '../../model/node.js';
@@ -290,7 +291,7 @@ function moveNode(st: KkState, m: number): void {
290
291
  function solveModel(g: Graph, st: KkState): void {
291
292
  for (let m = chooseNode(st); m >= 0; m = chooseNode(st)) moveNode(st, m);
292
293
  if (st.move === st.maxIter) {
293
- agwarningf(`Max. iterations (${st.maxIter}) reached on graph ${g.name}`);
294
+ agwarningf(`Max. iterations (${st.maxIter}) reached on graph ${agNameOf(g)}`);
294
295
  }
295
296
  }
296
297
 
@@ -6,7 +6,6 @@
6
6
  * @see lib/neatogen/neatoinit.c
7
7
  */
8
8
 
9
- import { lateDouble } from '../../common/nodeinit.js';
10
9
  import type { Graph } from '../../model/graph.js';
11
10
  import type { Node } from '../../model/node.js';
12
11
  import type { DirectedVtxData } from './opt-arrangement.js';
@@ -129,7 +128,7 @@ function addNeighbours(g: Graph, np: Node, entry: DirectedVtxData, o: GraphDataO
129
128
  if (ep.head === ep.tail) continue;
130
129
  const vp = ep.tail === np ? ep.head : ep.tail;
131
130
  const vid = vp.info.id!;
132
- const dist = o.haveLen ? lateDouble(ep.attrs.get('len'), 1.0, 0) : 1.0;
131
+ const dist = o.haveLen ? (ep.info.dist ?? 0) : 1.0; // ED_dist (calloc 0)
133
132
  const idx = seen.get(vid);
134
133
  if (idx !== undefined) {
135
134
  if (o.haveLen) entry.ewgts[idx] = Math.max(entry.ewgts[idx]!, dist);
@@ -11,21 +11,11 @@
11
11
  */
12
12
 
13
13
  import { crand } from '../../common/crand.js';
14
- import { fma as softFma } from '../../common/fma.js';
14
+ import { fma } from '../../common/fma.js';
15
15
  import type { VtxData } from './dijkstra.js';
16
16
 
17
17
  const fr = Math.fround;
18
18
 
19
- /**
20
- * fmadd: round(a * b + c) once. `common/fma` splits operands (Dekker), which
21
- * turns an infinite operand into NaN where the hardware fmadd gives inf, so
22
- * non-finite products and addends take the plain expression (exact then).
23
- */
24
- export function fma(a: number, b: number, c: number): number {
25
- const p = a * b;
26
- return Number.isFinite(p) && Number.isFinite(c) ? softFma(a, b, c) : p + c;
27
- }
28
-
29
19
  /** Subtract the mean (double precision). @see matrix_ops.c:orthog1 */
30
20
  export function orthog1(n: number, vec: Float64Array): void {
31
21
  let sum = 0;
@@ -11,13 +11,13 @@
11
11
  */
12
12
 
13
13
  import { crand } from '../../common/crand.js';
14
+ import { fma } from '../../common/fma.js';
14
15
  import { conjugateGradientF } from './conjgrad.js';
15
16
  import type { VtxData } from './dijkstra.js';
16
17
  import { computeApsp } from './kkutils.js';
17
18
  import {
18
19
  BLOCK_NONE,
19
20
  copyVector,
20
- fma,
21
21
  innerProduct,
22
22
  norm,
23
23
  scadd,
@@ -21,6 +21,7 @@ import type { SplineInfo, PolygonT, TextlabelT } from '../../common/types.js';
21
21
  import { polygonOutlineRing } from '../../common/poly-sizing.js';
22
22
  import { fma, fms } from '../../common/fma.js';
23
23
  import { ShapeKind } from '../../common/types.js';
24
+ import { setState, GVSPLINES } from '../../common/layout-state.js';
24
25
  import { esepFactor, type ExpandT } from './sep-factor.js';
25
26
  import { libmCos, libmSin } from '../../common/libm-trig.js';
26
27
  import type { Poly, VConfig } from '../../pathplan/types.js';
@@ -821,10 +822,11 @@ export function splineEdges(g: Graph): void {
821
822
  }
822
823
  // C splineEdges wrapper: coalesce equivalent edges before routing.
823
824
  coalesceEdges(g);
824
- if (edgetype === EDGETYPE_LINE) { RoutingHelper.straight(g, edgetype); return; }
825
+ if (edgetype === EDGETYPE_LINE) { RoutingHelper.straight(g, edgetype); setState(GVSPLINES); return; }
825
826
  // C: margin = esepFactor(g) — the edge separation, default {3.2, 3.2, add}.
826
827
  // @see lib/neatogen/neatosplines.c:746
827
828
  splineEdgesImpl(g, esepFactor(g), edgetype);
829
+ setState(GVSPLINES); // State = GVSPLINES, neatosplines.c:778 (also the LINE path)
828
830
  }
829
831
 
830
832
  // ---------------------------------------------------------------------------
@@ -14,6 +14,7 @@
14
14
  */
15
15
 
16
16
  import { RenderError } from '../../errors.js';
17
+ import { agNameOf } from '../../model/cgraph-ops.js';
17
18
  import type { Graph } from '../../model/graph.js';
18
19
  import { srand48 } from '../../common/random.js';
19
20
  import { registerRenderState } from '../../common/render-state.js';
@@ -147,7 +148,7 @@ export function neatoMode(g: Graph): number {
147
148
  if (str === undefined || str === '') return MODE_MAJOR;
148
149
  const mode = modeByName().get(str);
149
150
  if (mode !== undefined) return mode;
150
- agwarningf(`Illegal value ${str} for attribute "mode" in graph ${g.root.name} - ignored`);
151
+ agwarningf(`Illegal value ${str} for attribute "mode" in graph ${agNameOf(g.root)} - ignored`);
151
152
  return MODE_MAJOR;
152
153
  }
153
154
 
@@ -195,7 +196,7 @@ function warnCircuitDisconnected(g: Graph): void {
195
196
  circuitWarned.add(g.root);
196
197
  agerrLineOn(
197
198
  'warn',
198
- `graph ${g.root.name} is disconnected. Hence, the circuit model\n`
199
+ `graph ${agNameOf(g.root)} is disconnected. Hence, the circuit model\n`
199
200
  + 'is undefined. Reverting to the shortest path model.\n'
200
201
  + 'Alternatively, consider running neato using -Gpack=true or decomposing\n'
201
202
  + 'the graph into connected components.',