@knowvah/dot-engine 2.1.0 → 2.2.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.
- package/dist/common/libm-hypot.d.ts +3 -0
- package/dist/common/libm-hypot.d.ts.map +1 -0
- package/dist/common/poly-sizing.d.ts.map +1 -1
- package/dist/index.js +72 -49
- package/dist/index.js.map +4 -4
- package/dist/layout/fdp/derive.d.ts.map +1 -1
- package/dist/layout/fdp/fdp-model.d.ts +4 -4
- package/dist/layout/fdp/tlayout-init.d.ts.map +1 -1
- package/dist/layout/fdp/tlayout-parms.d.ts +4 -1
- package/dist/layout/fdp/tlayout-parms.d.ts.map +1 -1
- package/dist/layout/fdp/tlayout.d.ts +3 -4
- package/dist/layout/fdp/tlayout.d.ts.map +1 -1
- package/dist/layout/fdp/xlayout.d.ts.map +1 -1
- package/dist/render.js +70 -47
- package/dist/render.js.map +4 -4
- package/package.json +1 -1
- package/src/common/libm-hypot.ts +40 -0
- package/src/common/poly-sizing.ts +10 -10
- package/src/layout/fdp/derive.ts +5 -2
- package/src/layout/fdp/fdp-model.ts +4 -4
- package/src/layout/fdp/tlayout-init.ts +8 -6
- package/src/layout/fdp/tlayout-parms.ts +5 -2
- package/src/layout/fdp/tlayout.ts +18 -17
- package/src/layout/fdp/xlayout.ts +26 -22
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knowvah/dot-engine",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
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",
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EPL-2.0
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `hypot(a, b)` as the native build host's libm computes it, bit for bit.
|
|
5
|
+
*
|
|
6
|
+
* Graphviz calls libm `hypot`; V8's `Math.hypot` and `sqrt(a*a + b*b)` each
|
|
7
|
+
* round differently in the last bit for some inputs, and fdp's force loop
|
|
8
|
+
* amplifies one-ulp differences into visibly different layouts. The reference
|
|
9
|
+
* build's libm (macOS arm64 libSystem) matches this model exactly on 400k
|
|
10
|
+
* sampled inputs, including subnormal, near-overflow, zero, inf and NaN
|
|
11
|
+
* operands: order the magnitudes, rescale by 2^∓600 outside [2^-500, 2^500],
|
|
12
|
+
* then `sqrt(fma(small, small, big*big))`. It is within one ulp of the
|
|
13
|
+
* correctly rounded result (glibc's hypot can differ in that last bit).
|
|
14
|
+
*
|
|
15
|
+
* @see docs/known-divergences.md (host libm)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { fma } from './fma.js';
|
|
19
|
+
|
|
20
|
+
/** Rescale thresholds and factor of the reference libm. */
|
|
21
|
+
const BIG = 2 ** 500;
|
|
22
|
+
const SMALL = 2 ** -500;
|
|
23
|
+
const SCALE_UP = 2 ** 600;
|
|
24
|
+
const SCALE_DOWN = 2 ** -600;
|
|
25
|
+
|
|
26
|
+
/** libm `hypot(a, b)` of the native build host. */
|
|
27
|
+
export function libmHypot(a: number, b: number): number {
|
|
28
|
+
if (a === Infinity || a === -Infinity || b === Infinity || b === -Infinity) return Infinity;
|
|
29
|
+
let x = Math.abs(a);
|
|
30
|
+
let y = Math.abs(b);
|
|
31
|
+
if (x < y) [x, y] = [y, x];
|
|
32
|
+
if (x > BIG) return core(x * SCALE_DOWN, y * SCALE_DOWN) * SCALE_UP;
|
|
33
|
+
if (x < SMALL) return core(x * SCALE_UP, y * SCALE_UP) * SCALE_DOWN;
|
|
34
|
+
return core(x, y);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** sqrt(y·y + x·x) with the y·y product fused (x ≥ y ≥ 0). */
|
|
38
|
+
function core(x: number, y: number): number {
|
|
39
|
+
return Math.sqrt(fma(y, y, x * x));
|
|
40
|
+
}
|
|
@@ -23,6 +23,7 @@ import { STAR, CYLINDER } from './shapeData.js';
|
|
|
23
23
|
import { starVertices } from './poly-vertices.js';
|
|
24
24
|
import { fma } from './fma.js';
|
|
25
25
|
import { RADIANS } from './arith.js';
|
|
26
|
+
import { libmHypot } from './libm-hypot.js';
|
|
26
27
|
|
|
27
28
|
/** Whitespace in points around labels / between peripheries. @see lib/common/const.h:GAP */
|
|
28
29
|
export const GAP = 4;
|
|
@@ -277,16 +278,15 @@ export interface PolyGeom {
|
|
|
277
278
|
|
|
278
279
|
/** Distort, skew, orient, and scale one unit vertex. @see shapes.c:poly_init */
|
|
279
280
|
function transformUnitVertex(R: Point, g: PolyGeom, c: { skewdist: number; gdistortion: number; gskew: number }, bb: Point): Point {
|
|
280
|
-
|
|
281
|
+
// clang -ffp-contract=on fuses both multiply-adds of this one statement
|
|
282
|
+
// (the first product of the outer sum, as elsewhere in the port).
|
|
283
|
+
const D = { x: fma(R.x, fma(R.y, c.gdistortion, c.skewdist), R.y * c.gskew), y: R.y };
|
|
281
284
|
const alpha = RADIANS(g.orientation) + Math.atan2(D.y, D.x);
|
|
282
|
-
// C uses libm hypot(D.x, D.y)
|
|
283
|
-
//
|
|
284
|
-
//
|
|
285
|
-
//
|
|
286
|
-
|
|
287
|
-
// an exactly-on-boundary component height up a cell, reordering polyomino
|
|
288
|
-
// packing and swapping two circo/osage components. @see shapes.c:poly_init
|
|
289
|
-
const r = Math.sqrt(D.x * D.x + D.y * D.y);
|
|
285
|
+
// C uses libm hypot(D.x, D.y); libmHypot reproduces the host libm bit for bit.
|
|
286
|
+
// Neither V8's Math.hypot (hypot(0.35355339059327379, same) = 0.5+1ULP, which once
|
|
287
|
+
// inflated ND_ht by ~7e-15 and swapped two packed circo/osage components) nor
|
|
288
|
+
// sqrt(x²+y²) (off on e.g. the orientation=20 box) does. @see shapes.c:poly_init
|
|
289
|
+
const r = libmHypot(D.x, D.y);
|
|
290
290
|
return { x: r * Math.cos(alpha) * bb.x, y: r * Math.sin(alpha) * bb.y };
|
|
291
291
|
}
|
|
292
292
|
|
|
@@ -303,7 +303,7 @@ export function polygonVertices(
|
|
|
303
303
|
const sectorangle = (2 * Math.PI) / g.sides;
|
|
304
304
|
const sidelength = Math.sin(sectorangle / 2);
|
|
305
305
|
const c = {
|
|
306
|
-
skewdist:
|
|
306
|
+
skewdist: libmHypot(Math.abs(g.distortion) + Math.abs(g.skew), 1),
|
|
307
307
|
gdistortion: (g.distortion * SQRT2) / Math.cos(sectorangle / 2),
|
|
308
308
|
gskew: g.skew / 2,
|
|
309
309
|
};
|
package/src/layout/fdp/derive.ts
CHANGED
|
@@ -21,6 +21,7 @@ import { Node } from '../../model/node.js';
|
|
|
21
21
|
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
|
+
import { nodesInSeq } from '../dot/decomp.js';
|
|
24
25
|
import { layoutMeasurer } from '../../common/nodeinit.js';
|
|
25
26
|
import { inputscaleDivisor } from '../../common/utils-inputscale.js';
|
|
26
27
|
import {
|
|
@@ -145,7 +146,9 @@ function deriveClusterNodes(g: Graph, dg: Graph, infop: LayoutInfo): void {
|
|
|
145
146
|
* @see layout.c:deriveGraph (remaining-nodes loop)
|
|
146
147
|
*/
|
|
147
148
|
function deriveRealNodes(g: Graph, dg: Graph): boolean {
|
|
148
|
-
|
|
149
|
+
// agfstnode/agnxtnode: a cluster subgraph iterates in node sequence order,
|
|
150
|
+
// not statement order; it fixes derived-node creation (and so id) order.
|
|
151
|
+
for (const n of nodesInSeq(g)) {
|
|
149
152
|
if (getDnode(n) !== null) continue;
|
|
150
153
|
const par = getParent(n);
|
|
151
154
|
if (par !== null && par !== gdata(g).parent) {
|
|
@@ -195,7 +198,7 @@ function deriveEdge(dg: Graph, byPair: Map<string, Edge>, tl: Node, hd: Node): E
|
|
|
195
198
|
|
|
196
199
|
/** Derived edges. @see layout.c:deriveGraph (edge loop) */
|
|
197
200
|
function deriveEdges(g: Graph, dg: Graph, byPair: Map<string, Edge>): void {
|
|
198
|
-
for (const n of g
|
|
201
|
+
for (const n of nodesInSeq(g)) { // agfstnode order, as in deriveRealNodes
|
|
199
202
|
const tl = getDnode(n)!;
|
|
200
203
|
for (const e of n.outEdges(g)) {
|
|
201
204
|
const hd = getDnode(e.head)!;
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
* fdp engine data model — gdata, dndata, bport_t, xparams, and the
|
|
5
5
|
* accessor macros of the C private interface.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* Matches Graphviz after 15.0.0 (fdpParms.Mlimit lives in
|
|
8
|
+
* tlayout-parms.ts).
|
|
9
9
|
*
|
|
10
|
-
* @see lib/fdpgen/fdp.h
|
|
11
|
-
* @see lib/fdpgen/xlayout.h
|
|
10
|
+
* @see lib/fdpgen/fdp.h
|
|
11
|
+
* @see lib/fdpgen/xlayout.h
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import type { Graph } from '../../model/graph.js';
|
|
@@ -18,6 +18,7 @@ import type { Node } from '../../model/node.js';
|
|
|
18
18
|
import type { Edge } from '../../model/edge.js';
|
|
19
19
|
import type { Point } from '../../model/geom.js';
|
|
20
20
|
import { srand48, drand48 } from '../../common/random.js';
|
|
21
|
+
import { fma } from '../../common/fma.js';
|
|
21
22
|
import {
|
|
22
23
|
type Bport,
|
|
23
24
|
dndata,
|
|
@@ -53,8 +54,9 @@ function placeByNeighbors(g: Graph, np: Node, ctr: Point): void {
|
|
|
53
54
|
np.info.pos![0] = p.x;
|
|
54
55
|
np.info.pos![1] = p.y;
|
|
55
56
|
} else if (cnt === 1) {
|
|
56
|
-
|
|
57
|
-
np.info.pos![
|
|
57
|
+
// contracted (-ffp-contract=on): fmadd of the first product
|
|
58
|
+
np.info.pos![0] = fma(0.98, p.x, 0.1 * ctr.x);
|
|
59
|
+
np.info.pos![1] = fma(0.9, p.y, 0.1 * ctr.y);
|
|
58
60
|
} else {
|
|
59
61
|
const angle = Math.PI * 2.0 * drand48();
|
|
60
62
|
const radius = 0.9 * drand48();
|
|
@@ -76,8 +78,8 @@ function averagePlacedNeighbors(g: Graph, np: Node, p: Point): number {
|
|
|
76
78
|
const op = ep.head === np ? ep.tail : ep.head;
|
|
77
79
|
if (!hasPos(op)) continue;
|
|
78
80
|
if (cnt) {
|
|
79
|
-
p.x = (p.x
|
|
80
|
-
p.y = (p.y
|
|
81
|
+
p.x = fma(p.x, cnt, op.info.pos![0]!) / (cnt + 1); // contracted
|
|
82
|
+
p.y = fma(p.y, cnt, op.info.pos![1]!) / (cnt + 1);
|
|
81
83
|
} else {
|
|
82
84
|
p.x = op.info.pos![0]!;
|
|
83
85
|
p.y = op.info.pos![1]!;
|
|
@@ -162,8 +164,8 @@ function sizeBoundary(width: number, height: number): void {
|
|
|
162
164
|
function placeWithPorts(g: Graph, pp: Bport[], ctr: Point): void {
|
|
163
165
|
for (const port of pp) { /* position ports on ellipse */
|
|
164
166
|
const np = port.n;
|
|
165
|
-
np.info.pos![0] = parms.Wd
|
|
166
|
-
np.info.pos![1] = parms.Ht
|
|
167
|
+
np.info.pos![0] = fma(parms.Wd, Math.cos(port.alpha), ctr.x); // contracted
|
|
168
|
+
np.info.pos![1] = fma(parms.Ht, Math.sin(port.alpha), ctr.y);
|
|
167
169
|
dndata(np).pinned = P_SET;
|
|
168
170
|
}
|
|
169
171
|
for (const np of g.nodes.values()) {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* @see lib/fdpgen/tlayout.c (15.0.0) — parms_t, fdp_initParams,
|
|
11
11
|
* init_params, cool, reset_params
|
|
12
|
-
* @see lib/common/globals.c
|
|
12
|
+
* @see lib/common/globals.c — fdpParms defaults
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import type { Graph } from '../../model/graph.js';
|
|
@@ -41,7 +41,7 @@ export const INIT_RANDOM = 2;
|
|
|
41
41
|
const DFLT_SMODE = INIT_RANDOM;
|
|
42
42
|
|
|
43
43
|
// ---------------------------------------------------------------------------
|
|
44
|
-
// fdp_parms defaults — @see lib/common/globals.c
|
|
44
|
+
// fdp_parms defaults — @see lib/common/globals.c
|
|
45
45
|
// ---------------------------------------------------------------------------
|
|
46
46
|
|
|
47
47
|
/**
|
|
@@ -61,6 +61,9 @@ export const fdpParms = {
|
|
|
61
61
|
Tfact: 1.0,
|
|
62
62
|
K: -1.0,
|
|
63
63
|
T0: -1.0,
|
|
64
|
+
/** Distance beyond which repulsion is 0; HUGE_VAL unless `dot -Lm` (no library
|
|
65
|
+
* equivalent). @see lib/fdpgen/fdp.h:fdpParms_s.Mlimit */
|
|
66
|
+
Mlimit: Infinity,
|
|
64
67
|
};
|
|
65
68
|
|
|
66
69
|
// ---------------------------------------------------------------------------
|
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
* fdp initial layout — grid-accelerated Fruchterman–Reingold on point
|
|
5
5
|
* nodes with boundary ports.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* (
|
|
9
|
-
* 15.0.0 output, so neither is ported.
|
|
7
|
+
* Repulsion follows Graphviz after 15.0.0: doRep takes the hypot distance
|
|
8
|
+
* (the host libm's, see libm-hypot.ts) and honours the Mlimit cutoff.
|
|
10
9
|
*
|
|
11
10
|
* All math is double precision (unlike neato's float32 stress kernel).
|
|
12
11
|
* Force-accumulation order is load-bearing: node iteration is the
|
|
@@ -16,13 +15,14 @@
|
|
|
16
15
|
* Parameter state lives in tlayout-parms.ts; initial placement in
|
|
17
16
|
* tlayout-init.ts.
|
|
18
17
|
*
|
|
19
|
-
* @see lib/fdpgen/tlayout.c
|
|
18
|
+
* @see lib/fdpgen/tlayout.c
|
|
20
19
|
*/
|
|
21
20
|
|
|
22
21
|
import type { Graph } from '../../model/graph.js';
|
|
23
22
|
import type { Node } from '../../model/node.js';
|
|
24
23
|
import type { Edge } from '../../model/edge.js';
|
|
25
24
|
import { fma, fms } from '../../common/fma.js';
|
|
25
|
+
import { libmHypot } from '../../common/libm-hypot.js';
|
|
26
26
|
import { Grid, type Cell } from './grid.js';
|
|
27
27
|
import {
|
|
28
28
|
type Bport,
|
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
gdata,
|
|
34
34
|
P_FIX,
|
|
35
35
|
} from './fdp-model.js';
|
|
36
|
-
import { parms, cool, initParams, resetParams } from './tlayout-parms.js';
|
|
36
|
+
import { parms, cool, initParams, resetParams, fdpParms } from './tlayout-parms.js';
|
|
37
37
|
import { initPositions } from './tlayout-init.js';
|
|
38
38
|
import { crand } from '../../common/crand.js';
|
|
39
39
|
|
|
@@ -66,25 +66,26 @@ export function coincidentDelta(): { xdelta: number; ydelta: number } {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
/**
|
|
69
|
-
* Repulsive force = K²/d (useNew) or K²/d
|
|
69
|
+
* Repulsive force = K²/d² (useNew) or K²/d, zero beyond Mlimit.
|
|
70
70
|
* @see lib/fdpgen/tlayout.c:doRep
|
|
71
71
|
*/
|
|
72
72
|
function doRep(
|
|
73
|
-
p: Node, q: Node, xdelta: number, ydelta: number,
|
|
73
|
+
p: Node, q: Node, xdelta: number, ydelta: number, dist: number,
|
|
74
74
|
): void {
|
|
75
75
|
let force: number;
|
|
76
76
|
|
|
77
|
-
if (
|
|
78
|
-
const d = coincidentDelta();
|
|
77
|
+
if (!(dist > 0)) {
|
|
78
|
+
const d = coincidentDelta(); // C re-rolls rand() until hypot > 0
|
|
79
79
|
xdelta = d.xdelta;
|
|
80
80
|
ydelta = d.ydelta;
|
|
81
|
-
|
|
81
|
+
dist = libmHypot(xdelta, ydelta);
|
|
82
82
|
}
|
|
83
|
-
if (
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
if (dist > fdpParms.Mlimit) {
|
|
84
|
+
force = 0;
|
|
85
|
+
} else if (parms.useNew) {
|
|
86
|
+
force = parms.K * parms.K / (dist * dist * dist);
|
|
86
87
|
} else {
|
|
87
|
-
force = parms.K * parms.K /
|
|
88
|
+
force = parms.K * parms.K / (dist * dist);
|
|
88
89
|
}
|
|
89
90
|
if (isPort(p) && isPort(q)) force *= 10.0;
|
|
90
91
|
const dq = disp(q);
|
|
@@ -100,7 +101,7 @@ function doRep(
|
|
|
100
101
|
function applyRep(p: Node, q: Node): void {
|
|
101
102
|
const xdelta = q.info.pos![0]! - p.info.pos![0]!;
|
|
102
103
|
const ydelta = q.info.pos![1]! - p.info.pos![1]!;
|
|
103
|
-
doRep(p, q, xdelta, ydelta,
|
|
104
|
+
doRep(p, q, xdelta, ydelta, libmHypot(xdelta, ydelta));
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
/** @see lib/fdpgen/tlayout.c:doNeighbor */
|
|
@@ -111,8 +112,8 @@ function doNeighbor(grid: Grid, i: number, j: number, nodes: Node[]): void {
|
|
|
111
112
|
for (const q of cellp.nodes) {
|
|
112
113
|
const xdelta = q.info.pos![0]! - p.info.pos![0]!;
|
|
113
114
|
const ydelta = q.info.pos![1]! - p.info.pos![1]!;
|
|
114
|
-
const
|
|
115
|
-
if (
|
|
115
|
+
const dist = libmHypot(xdelta, ydelta);
|
|
116
|
+
if (dist < parms.Cell) doRep(p, q, xdelta, ydelta, dist);
|
|
116
117
|
}
|
|
117
118
|
}
|
|
118
119
|
}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* fdp expansion layout: grow the point-node layout so sized nodes do
|
|
5
5
|
* not overlap, by force iteration with overlap-aware repulsion.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* Follows Graphviz after 15.0.0: repulsion uses the hypot distance (the
|
|
8
|
+
* host libm's, see libm-hypot.ts) and the Mlimit cutoff.
|
|
9
9
|
*
|
|
10
10
|
* The default overlap attribute is "9:prism": up to 9 x_layout tries,
|
|
11
11
|
* then removeOverlapAs with the remaining mode. x_layout does NOT always
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* fdpAdjust, the scale family via scAdjust. On the GTS reference build
|
|
15
15
|
* overlap=false resolves to AM_PRISM (value 1000), not a no-op.
|
|
16
16
|
*
|
|
17
|
-
* @see lib/fdpgen/xlayout.c
|
|
17
|
+
* @see lib/fdpgen/xlayout.c
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import { RenderError } from '../../errors.js';
|
|
@@ -31,6 +31,9 @@ import {
|
|
|
31
31
|
P_PIN,
|
|
32
32
|
} from './fdp-model.js';
|
|
33
33
|
import { coincidentDelta } from './tlayout.js';
|
|
34
|
+
import { fdpParms } from './tlayout-parms.js';
|
|
35
|
+
import { libmHypot } from '../../common/libm-hypot.js';
|
|
36
|
+
import { fma, fms } from '../../common/fma.js';
|
|
34
37
|
import { normalizeG } from './normalize.js';
|
|
35
38
|
import { simpleScale } from '../neato/simple-scale.js';
|
|
36
39
|
|
|
@@ -76,7 +79,7 @@ function ht2(n: Node): number {
|
|
|
76
79
|
|
|
77
80
|
/** Expanded radius of n. @see lib/fdpgen/xlayout.c:RAD */
|
|
78
81
|
function rad(n: Node): number {
|
|
79
|
-
return
|
|
82
|
+
return libmHypot(wd2(n), ht2(n));
|
|
80
83
|
}
|
|
81
84
|
|
|
82
85
|
/**
|
|
@@ -129,31 +132,32 @@ function cntOverlaps(g: Graph): number {
|
|
|
129
132
|
// ---------------------------------------------------------------------------
|
|
130
133
|
|
|
131
134
|
/**
|
|
132
|
-
* Overlap-aware repulsion; returns 1 if the pair overlaps.
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
* coincident nodes (coincidentDelta), shared with tlayout.
|
|
135
|
+
* Overlap-aware repulsion; returns 1 if the pair overlaps. Zero beyond
|
|
136
|
+
* Mlimit. C's applyRep (delta + hypot) is inlined; its doRep re-rolls rand()
|
|
137
|
+
* deltas while the distance is not positive (coincidentDelta).
|
|
136
138
|
* @see lib/fdpgen/xlayout.c:doRep
|
|
137
139
|
* @see lib/fdpgen/xlayout.c:applyRep
|
|
138
140
|
*/
|
|
139
141
|
function applyRep(p: Node, q: Node, rs: RepStrength): number {
|
|
140
142
|
let xdelta = q.info.pos![0]! - p.info.pos![0]!;
|
|
141
143
|
let ydelta = q.info.pos![1]! - p.info.pos![1]!;
|
|
142
|
-
let
|
|
143
|
-
if (
|
|
144
|
+
let dist = libmHypot(xdelta, ydelta);
|
|
145
|
+
if (!(dist > 0)) {
|
|
144
146
|
const d = coincidentDelta();
|
|
145
147
|
xdelta = d.xdelta;
|
|
146
148
|
ydelta = d.ydelta;
|
|
147
|
-
|
|
149
|
+
dist = libmHypot(xdelta, ydelta);
|
|
148
150
|
}
|
|
149
151
|
const ov = overlap(p, q) ? 1 : 0;
|
|
150
|
-
|
|
152
|
+
let force = ov ? rs.ov / (dist * dist) : rs.nonov / (dist * dist);
|
|
153
|
+
if (dist > fdpParms.Mlimit) force = 0;
|
|
151
154
|
const dq = disp(q);
|
|
152
155
|
const dp = disp(p);
|
|
153
|
-
|
|
154
|
-
dq[
|
|
155
|
-
|
|
156
|
-
dp[
|
|
156
|
+
// arm64 contraction (-ffp-contract=on): DISP ± delta·force are fmadd/fmsub
|
|
157
|
+
dq[0] = fma(xdelta, force, dq[0]!);
|
|
158
|
+
dq[1] = fma(ydelta, force, dq[1]!);
|
|
159
|
+
dp[0] = fms(xdelta, force, dp[0]!);
|
|
160
|
+
dp[1] = fms(ydelta, force, dp[1]!);
|
|
157
161
|
return ov;
|
|
158
162
|
}
|
|
159
163
|
|
|
@@ -165,16 +169,16 @@ function applyAttr(p: Node, q: Node): void {
|
|
|
165
169
|
if (overlap(p, q)) return;
|
|
166
170
|
const xdelta = q.info.pos![0]! - p.info.pos![0]!;
|
|
167
171
|
const ydelta = q.info.pos![1]! - p.info.pos![1]!;
|
|
168
|
-
const dist =
|
|
172
|
+
const dist = libmHypot(xdelta, ydelta);
|
|
169
173
|
const din = rad(p) + rad(q);
|
|
170
174
|
const dout = dist - din;
|
|
171
175
|
const force = dout * dout / ((xParams.K + din) * dist);
|
|
172
176
|
const dq = disp(q);
|
|
173
177
|
const dp = disp(p);
|
|
174
|
-
dq[0]
|
|
175
|
-
dq[1]
|
|
176
|
-
dp[0]
|
|
177
|
-
dp[1]
|
|
178
|
+
dq[0] = fms(xdelta, force, dq[0]!);
|
|
179
|
+
dq[1] = fms(ydelta, force, dq[1]!);
|
|
180
|
+
dp[0] = fma(xdelta, force, dp[0]!);
|
|
181
|
+
dp[1] = fma(ydelta, force, dp[1]!);
|
|
178
182
|
}
|
|
179
183
|
|
|
180
184
|
// ---------------------------------------------------------------------------
|
|
@@ -204,7 +208,7 @@ function moveNodes(g: Graph, temp: number): void {
|
|
|
204
208
|
if (dndata(n).pinned === P_PIN) continue;
|
|
205
209
|
const dx = disp(n)[0];
|
|
206
210
|
const dy = disp(n)[1];
|
|
207
|
-
const len2 = dx
|
|
211
|
+
const len2 = fma(dx, dx, dy * dy); // contracted, as in tlayout
|
|
208
212
|
if (len2 < temp2) {
|
|
209
213
|
n.info.pos![0]! += dx;
|
|
210
214
|
n.info.pos![1]! += dy;
|