@graphty/layout 1.2.8 → 1.3.2
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 +394 -404
- package/dist/layout.js +881 -821
- package/dist/layout.js.map +1 -1
- package/dist/src/algorithms/index.d.ts +2 -2
- package/dist/src/algorithms/index.js +2 -2
- package/dist/src/algorithms/index.js.map +1 -1
- package/dist/src/algorithms/optimization/index.d.ts +4 -4
- package/dist/src/algorithms/optimization/index.js +3 -3
- package/dist/src/algorithms/optimization/index.js.map +1 -1
- package/dist/src/algorithms/optimization/kamada-kawai-solver.d.ts +2 -5
- package/dist/src/algorithms/optimization/kamada-kawai-solver.js +6 -9
- package/dist/src/algorithms/optimization/kamada-kawai-solver.js.map +1 -1
- package/dist/src/algorithms/optimization/lbfgs.d.ts +0 -1
- package/dist/src/algorithms/optimization/lbfgs.js +3 -5
- package/dist/src/algorithms/optimization/lbfgs.js.map +1 -1
- package/dist/src/algorithms/optimization/line-search.d.ts +0 -1
- package/dist/src/algorithms/optimization/line-search.js +0 -1
- package/dist/src/algorithms/optimization/line-search.js.map +1 -1
- package/dist/src/algorithms/optimization/types.d.ts +1 -1
- package/dist/src/algorithms/planarity/check.d.ts +3 -3
- package/dist/src/algorithms/planarity/check.js +7 -7
- package/dist/src/algorithms/planarity/check.js.map +1 -1
- package/dist/src/algorithms/planarity/embedding.d.ts +3 -5
- package/dist/src/algorithms/planarity/embedding.js +20 -16
- package/dist/src/algorithms/planarity/embedding.js.map +1 -1
- package/dist/src/algorithms/planarity/index.d.ts +4 -4
- package/dist/src/algorithms/planarity/index.js +4 -4
- package/dist/src/algorithms/planarity/index.js.map +1 -1
- package/dist/src/algorithms/planarity/lr-test.d.ts +3 -3
- package/dist/src/algorithms/planarity/lr-test.js +5 -5
- package/dist/src/algorithms/planarity/lr-test.js.map +1 -1
- package/dist/src/algorithms/planarity/special-graphs.d.ts +1 -4
- package/dist/src/algorithms/planarity/special-graphs.js +18 -15
- package/dist/src/algorithms/planarity/special-graphs.js.map +1 -1
- package/dist/src/generators/basic.d.ts +1 -1
- package/dist/src/generators/basic.js +4 -4
- package/dist/src/generators/basic.js.map +1 -1
- package/dist/src/generators/bipartite.d.ts +1 -1
- package/dist/src/generators/bipartite.js +2 -2
- package/dist/src/generators/bipartite.js.map +1 -1
- package/dist/src/generators/grid.d.ts +1 -1
- package/dist/src/generators/grid.js +1 -1
- package/dist/src/generators/grid.js.map +1 -1
- package/dist/src/generators/index.d.ts +5 -5
- package/dist/src/generators/index.js +5 -5
- package/dist/src/generators/index.js.map +1 -1
- package/dist/src/generators/random.d.ts +1 -1
- package/dist/src/generators/random.js +2 -2
- package/dist/src/generators/random.js.map +1 -1
- package/dist/src/generators/scale-free.d.ts +1 -1
- package/dist/src/generators/scale-free.js +4 -4
- package/dist/src/generators/scale-free.js.map +1 -1
- package/dist/src/index.d.ts +4 -4
- package/dist/src/index.js +4 -4
- package/dist/src/layouts/basic/index.d.ts +1 -1
- package/dist/src/layouts/basic/index.js +1 -1
- package/dist/src/layouts/basic/random.d.ts +1 -2
- package/dist/src/layouts/basic/random.js +3 -4
- package/dist/src/layouts/basic/random.js.map +1 -1
- package/dist/src/layouts/force-directed/arf.d.ts +1 -2
- package/dist/src/layouts/force-directed/arf.js +13 -8
- package/dist/src/layouts/force-directed/arf.js.map +1 -1
- package/dist/src/layouts/force-directed/forceatlas2.d.ts +1 -2
- package/dist/src/layouts/force-directed/forceatlas2.js +58 -35
- package/dist/src/layouts/force-directed/forceatlas2.js.map +1 -1
- package/dist/src/layouts/force-directed/fruchterman-reingold.d.ts +11 -12
- package/dist/src/layouts/force-directed/fruchterman-reingold.js +17 -17
- package/dist/src/layouts/force-directed/fruchterman-reingold.js.map +1 -1
- package/dist/src/layouts/force-directed/index.d.ts +5 -5
- package/dist/src/layouts/force-directed/index.js +5 -5
- package/dist/src/layouts/force-directed/index.js.map +1 -1
- package/dist/src/layouts/force-directed/kamada-kawai.d.ts +2 -3
- package/dist/src/layouts/force-directed/kamada-kawai.js +12 -10
- package/dist/src/layouts/force-directed/kamada-kawai.js.map +1 -1
- package/dist/src/layouts/force-directed/spring.d.ts +11 -12
- package/dist/src/layouts/force-directed/spring.js +11 -12
- package/dist/src/layouts/force-directed/spring.js.map +1 -1
- package/dist/src/layouts/geometric/circular.d.ts +1 -2
- package/dist/src/layouts/geometric/circular.js +10 -9
- package/dist/src/layouts/geometric/circular.js.map +1 -1
- package/dist/src/layouts/geometric/index.d.ts +3 -3
- package/dist/src/layouts/geometric/index.js +3 -3
- package/dist/src/layouts/geometric/shell.d.ts +1 -2
- package/dist/src/layouts/geometric/shell.js +5 -5
- package/dist/src/layouts/geometric/shell.js.map +1 -1
- package/dist/src/layouts/geometric/spiral.d.ts +1 -2
- package/dist/src/layouts/geometric/spiral.js +5 -9
- package/dist/src/layouts/geometric/spiral.js.map +1 -1
- package/dist/src/layouts/hierarchical/bfs.d.ts +2 -3
- package/dist/src/layouts/hierarchical/bfs.js +5 -6
- package/dist/src/layouts/hierarchical/bfs.js.map +1 -1
- package/dist/src/layouts/hierarchical/bipartite.d.ts +2 -3
- package/dist/src/layouts/hierarchical/bipartite.js +8 -9
- package/dist/src/layouts/hierarchical/bipartite.js.map +1 -1
- package/dist/src/layouts/hierarchical/index.d.ts +3 -3
- package/dist/src/layouts/hierarchical/index.js +3 -3
- package/dist/src/layouts/hierarchical/index.js.map +1 -1
- package/dist/src/layouts/hierarchical/multipartite.d.ts +2 -3
- package/dist/src/layouts/hierarchical/multipartite.js +7 -8
- package/dist/src/layouts/hierarchical/multipartite.js.map +1 -1
- package/dist/src/layouts/index.d.ts +5 -5
- package/dist/src/layouts/index.js +5 -5
- package/dist/src/layouts/index.js.map +1 -1
- package/dist/src/layouts/specialized/index.d.ts +2 -2
- package/dist/src/layouts/specialized/index.js +2 -2
- package/dist/src/layouts/specialized/index.js.map +1 -1
- package/dist/src/layouts/specialized/planar.d.ts +3 -3
- package/dist/src/layouts/specialized/planar.js +8 -8
- package/dist/src/layouts/specialized/planar.js.map +1 -1
- package/dist/src/layouts/specialized/spectral.d.ts +1 -2
- package/dist/src/layouts/specialized/spectral.js +30 -20
- package/dist/src/layouts/specialized/spectral.js.map +1 -1
- package/dist/src/types/embedding.d.ts +1 -1
- package/dist/src/types/index.d.ts +3 -3
- package/dist/src/types/index.js +3 -3
- package/dist/src/types/index.js.map +1 -1
- package/dist/src/types/layout.d.ts +1 -1
- package/dist/src/utils/graph.d.ts +1 -5
- package/dist/src/utils/graph.js +4 -6
- package/dist/src/utils/graph.js.map +1 -1
- package/dist/src/utils/index.d.ts +5 -5
- package/dist/src/utils/index.js +5 -5
- package/dist/src/utils/index.js.map +1 -1
- package/dist/src/utils/numpy.js +22 -16
- package/dist/src/utils/numpy.js.map +1 -1
- package/dist/src/utils/params.d.ts +1 -2
- package/dist/src/utils/params.js +0 -1
- package/dist/src/utils/params.js.map +1 -1
- package/dist/src/utils/random.d.ts +17 -0
- package/dist/src/utils/random.js +18 -1
- package/dist/src/utils/random.js.map +1 -1
- package/dist/src/utils/rescale.d.ts +1 -3
- package/dist/src/utils/rescale.js +12 -10
- package/dist/src/utils/rescale.js.map +1 -1
- package/dist/vitest.config.js +14 -14
- package/dist/vitest.config.js.map +1 -1
- package/package.json +29 -24
- package/src/algorithms/index.ts +2 -2
- package/src/algorithms/optimization/index.ts +4 -8
- package/src/algorithms/optimization/kamada-kawai-solver.ts +170 -179
- package/src/algorithms/optimization/lbfgs.ts +41 -48
- package/src/algorithms/optimization/line-search.ts +27 -28
- package/src/algorithms/optimization/types.ts +2 -2
- package/src/algorithms/planarity/check.ts +22 -21
- package/src/algorithms/planarity/embedding.ts +159 -165
- package/src/algorithms/planarity/index.ts +4 -8
- package/src/algorithms/planarity/lr-test.ts +46 -45
- package/src/algorithms/planarity/special-graphs.ts +73 -83
- package/src/generators/basic.ts +57 -57
- package/src/generators/bipartite.ts +37 -31
- package/src/generators/grid.ts +28 -28
- package/src/generators/index.ts +5 -11
- package/src/generators/random.ts +26 -25
- package/src/generators/scale-free.ts +56 -55
- package/src/index.ts +5 -5
- package/src/layouts/basic/index.ts +1 -1
- package/src/layouts/basic/random.ts +20 -16
- package/src/layouts/force-directed/arf.ts +107 -105
- package/src/layouts/force-directed/forceatlas2.ts +365 -351
- package/src/layouts/force-directed/fruchterman-reingold.ts +133 -134
- package/src/layouts/force-directed/index.ts +5 -5
- package/src/layouts/force-directed/kamada-kawai.ts +78 -80
- package/src/layouts/force-directed/spring.ts +24 -25
- package/src/layouts/geometric/circular.ts +63 -57
- package/src/layouts/geometric/index.ts +3 -3
- package/src/layouts/geometric/shell.ts +58 -53
- package/src/layouts/geometric/spiral.ts +60 -64
- package/src/layouts/hierarchical/bfs.ts +58 -59
- package/src/layouts/hierarchical/bipartite.ts +62 -63
- package/src/layouts/hierarchical/index.ts +3 -3
- package/src/layouts/hierarchical/multipartite.ts +62 -63
- package/src/layouts/index.ts +5 -5
- package/src/layouts/specialized/index.ts +2 -2
- package/src/layouts/specialized/planar.ts +42 -41
- package/src/layouts/specialized/spectral.ts +110 -99
- package/src/types/embedding.ts +5 -5
- package/src/types/graph.ts +4 -4
- package/src/types/index.ts +3 -3
- package/src/types/layout.ts +2 -2
- package/src/utils/graph.ts +31 -35
- package/src/utils/index.ts +5 -5
- package/src/utils/numpy.ts +96 -92
- package/src/utils/params.ts +14 -11
- package/src/utils/random.ts +57 -40
- package/src/utils/rescale.ts +169 -174
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Scale-free graph generation function
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { Graph, Node
|
|
5
|
+
import { Edge,Graph, Node } from "../types";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Create a scale-free graph using Barabási-Albert model
|
|
@@ -12,61 +12,62 @@ import { Graph, Node, Edge } from '../types';
|
|
|
12
12
|
* @returns Graph object with scale-free properties
|
|
13
13
|
*/
|
|
14
14
|
export function scaleFreeGraph(n: number, m: number, seed?: number): Graph {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const nodes: Node[] = Array.from({ length: n }, (_, i) => i);
|
|
20
|
-
const edges: Edge[] = [];
|
|
21
|
-
const degrees = new Array(n).fill(0);
|
|
22
|
-
|
|
23
|
-
// Simple deterministic pseudo-random if seed provided
|
|
24
|
-
let currentSeed = seed;
|
|
25
|
-
let random = seed !== undefined
|
|
26
|
-
? () => {
|
|
27
|
-
currentSeed = (currentSeed! * 9301 + 49297) % 233280;
|
|
28
|
-
return currentSeed / 233280;
|
|
29
|
-
}
|
|
30
|
-
: Math.random;
|
|
31
|
-
|
|
32
|
-
// Start with complete graph of m+1 nodes
|
|
33
|
-
for (let i = 0; i <= m; i++) {
|
|
34
|
-
for (let j = i + 1; j <= m; j++) {
|
|
35
|
-
edges.push([i, j]);
|
|
36
|
-
degrees[i]++;
|
|
37
|
-
degrees[j]++;
|
|
15
|
+
if (m >= n) {
|
|
16
|
+
throw new Error("m must be less than n");
|
|
38
17
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
18
|
+
|
|
19
|
+
const nodes: Node[] = Array.from({ length: n }, (_, i) => i);
|
|
20
|
+
const edges: Edge[] = [];
|
|
21
|
+
const degrees = new Array(n).fill(0);
|
|
22
|
+
|
|
23
|
+
// Simple deterministic pseudo-random if seed provided
|
|
24
|
+
let currentSeed = seed;
|
|
25
|
+
const random =
|
|
26
|
+
seed !== undefined
|
|
27
|
+
? () => {
|
|
28
|
+
currentSeed = (currentSeed! * 9301 + 49297) % 233280;
|
|
29
|
+
return currentSeed / 233280;
|
|
30
|
+
}
|
|
31
|
+
: Math.random;
|
|
32
|
+
|
|
33
|
+
// Start with complete graph of m+1 nodes
|
|
34
|
+
for (let i = 0; i <= m; i++) {
|
|
35
|
+
for (let j = i + 1; j <= m; j++) {
|
|
36
|
+
edges.push([i, j]);
|
|
37
|
+
degrees[i]++;
|
|
38
|
+
degrees[j]++;
|
|
56
39
|
}
|
|
57
|
-
}
|
|
58
40
|
}
|
|
59
|
-
|
|
60
|
-
// Add
|
|
61
|
-
for (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
41
|
+
|
|
42
|
+
// Add remaining nodes
|
|
43
|
+
for (let i = m + 1; i < n; i++) {
|
|
44
|
+
const targets = new Set<number>();
|
|
45
|
+
const totalDegree = degrees.reduce((sum, d) => sum + d, 0);
|
|
46
|
+
|
|
47
|
+
// Choose m targets based on preferential attachment
|
|
48
|
+
while (targets.size < m) {
|
|
49
|
+
const r = random() * totalDegree;
|
|
50
|
+
let cumSum = 0;
|
|
51
|
+
|
|
52
|
+
for (let j = 0; j < i; j++) {
|
|
53
|
+
cumSum += degrees[j];
|
|
54
|
+
if (r <= cumSum && !targets.has(j)) {
|
|
55
|
+
targets.add(j);
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Add edges to targets
|
|
62
|
+
for (const target of targets) {
|
|
63
|
+
edges.push([i, target]);
|
|
64
|
+
degrees[i]++;
|
|
65
|
+
degrees[target]++;
|
|
66
|
+
}
|
|
65
67
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
nodes: () => nodes,
|
|
71
|
+
edges: () => edges,
|
|
72
|
+
};
|
|
73
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Main entry point for @graphty/layout
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* This file exports the complete public API, maintaining
|
|
5
5
|
* compatibility with the original layout.ts file.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
// Re-export all types
|
|
9
|
-
export * from
|
|
9
|
+
export * from "./types";
|
|
10
10
|
|
|
11
11
|
// Re-export utilities that are part of the public API
|
|
12
|
-
export { rescaleLayout, rescaleLayoutDict } from
|
|
12
|
+
export { rescaleLayout, rescaleLayoutDict } from "./utils/rescale";
|
|
13
13
|
|
|
14
14
|
// Re-export all layout algorithms
|
|
15
|
-
export * from
|
|
15
|
+
export * from "./layouts";
|
|
16
16
|
|
|
17
17
|
// Re-export all graph generation functions
|
|
18
|
-
export * from
|
|
18
|
+
export * from "./generators";
|
|
@@ -2,31 +2,35 @@
|
|
|
2
2
|
* Random layout algorithm
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { Graph, Node, PositionMap } from
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { RandomNumberGenerator } from
|
|
5
|
+
import { Graph, Node, PositionMap } from "../../types";
|
|
6
|
+
import { getNodesFromGraph } from "../../utils/graph";
|
|
7
|
+
import { _processParams } from "../../utils/params";
|
|
8
|
+
import { RandomNumberGenerator } from "../../utils/random";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Position nodes uniformly at random in the unit square.
|
|
12
|
-
*
|
|
13
12
|
* @param G - Graph or list of nodes
|
|
14
13
|
* @param center - Coordinate pair around which to center the layout
|
|
15
14
|
* @param dim - Dimension of layout
|
|
16
15
|
* @param seed - Random seed for reproducible layouts
|
|
17
16
|
* @returns Positions dictionary keyed by node
|
|
18
17
|
*/
|
|
19
|
-
export function randomLayout(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
export function randomLayout(
|
|
19
|
+
G: Graph | Node[],
|
|
20
|
+
center: number[] | null = null,
|
|
21
|
+
dim: number = 2,
|
|
22
|
+
seed: number | null = null,
|
|
23
|
+
): PositionMap {
|
|
24
|
+
const processed = _processParams(G, center, dim);
|
|
25
|
+
const nodes = getNodesFromGraph(processed.G);
|
|
26
|
+
center = processed.center;
|
|
23
27
|
|
|
24
|
-
|
|
25
|
-
|
|
28
|
+
const rng = new RandomNumberGenerator(seed ?? undefined);
|
|
29
|
+
const pos: PositionMap = {};
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
nodes.forEach((node: Node) => {
|
|
32
|
+
pos[node] = (rng.rand(dim) as number[]).map((val: number, i: number) => val + center[i]);
|
|
33
|
+
});
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
}
|
|
35
|
+
return pos;
|
|
36
|
+
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import type { Graph, Node, PositionMap } from
|
|
2
|
-
import { getNodesFromGraph
|
|
3
|
-
import { RandomNumberGenerator } from
|
|
4
|
-
import { randomLayout } from
|
|
1
|
+
import type { Graph, Node, PositionMap } from "../../types";
|
|
2
|
+
import { getEdgesFromGraph,getNodesFromGraph } from "../../utils/graph";
|
|
3
|
+
import { RandomNumberGenerator } from "../../utils/random";
|
|
4
|
+
import { randomLayout } from "../basic/random";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Layout algorithm with attractive and repulsive forces (ARF).
|
|
8
|
-
*
|
|
9
8
|
* @param G - Graph
|
|
10
9
|
* @param pos - Initial positions for nodes
|
|
11
10
|
* @param scaling - Scale factor for positions
|
|
@@ -15,116 +14,119 @@ import { randomLayout } from '../basic/random';
|
|
|
15
14
|
* @returns Positions dictionary keyed by node
|
|
16
15
|
*/
|
|
17
16
|
export function arfLayout(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
G: Graph,
|
|
18
|
+
pos: PositionMap | null = null,
|
|
19
|
+
scaling: number = 1,
|
|
20
|
+
a: number = 1.1,
|
|
21
|
+
maxIter: number = 1000,
|
|
22
|
+
seed: number | null = null,
|
|
24
23
|
): PositionMap {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
24
|
+
if (a <= 1) {
|
|
25
|
+
throw new Error("The parameter a should be larger than 1");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const nodes = getNodesFromGraph(G);
|
|
29
|
+
const edges = getEdgesFromGraph(G);
|
|
30
|
+
|
|
31
|
+
if (nodes.length === 0) {
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Initialize positions if not provided
|
|
36
|
+
if (!pos) {
|
|
37
|
+
pos = randomLayout(G, null, 2, seed);
|
|
38
|
+
} else {
|
|
39
|
+
// Make sure all nodes have positions
|
|
40
|
+
const rng = new RandomNumberGenerator(seed ?? undefined);
|
|
41
|
+
const defaultPos: PositionMap = {};
|
|
42
|
+
nodes.forEach((node: Node) => {
|
|
43
|
+
if (!pos![node]) {
|
|
44
|
+
defaultPos[node] = [rng.rand() as number, rng.rand() as number];
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
pos = { ...pos, ...defaultPos };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Create node index mapping
|
|
51
|
+
const nodeIndex: Record<Node, number> = {};
|
|
52
|
+
nodes.forEach((node: Node, i: number) => {
|
|
53
|
+
nodeIndex[node] = i;
|
|
47
54
|
});
|
|
48
|
-
pos = { ...pos, ...defaultPos };
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Create node index mapping
|
|
52
|
-
const nodeIndex: Record<Node, number> = {};
|
|
53
|
-
nodes.forEach((node: Node, i: number) => {
|
|
54
|
-
nodeIndex[node] = i;
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
// Create positions array
|
|
58
|
-
const positions: number[][] = nodes.map((node: Node) => [...pos![node]]);
|
|
59
|
-
|
|
60
|
-
// Initialize spring constant matrix
|
|
61
|
-
const N = nodes.length;
|
|
62
|
-
const K = Array(N).fill(0).map(() => Array(N).fill(1));
|
|
63
|
-
|
|
64
|
-
// Set diagonal to zero (no self-attraction)
|
|
65
|
-
for (let i = 0; i < N; i++) {
|
|
66
|
-
K[i][i] = 0;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Set stronger attraction between connected nodes
|
|
70
|
-
for (const [source, target] of edges) {
|
|
71
|
-
if (source === target) continue;
|
|
72
|
-
|
|
73
|
-
const i = nodeIndex[source];
|
|
74
|
-
const j = nodeIndex[target];
|
|
75
|
-
K[i][j] = a;
|
|
76
|
-
K[j][i] = a;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Calculate rho (scale factor)
|
|
80
|
-
const rho = scaling * Math.sqrt(N);
|
|
81
|
-
|
|
82
|
-
// Optimization loop
|
|
83
|
-
const dt = 1e-3; // Time step
|
|
84
|
-
const etol = 1e-6; // Error tolerance
|
|
85
|
-
let error = etol + 1;
|
|
86
|
-
let nIter = 0;
|
|
87
|
-
|
|
88
|
-
while (error > etol && nIter < maxIter) {
|
|
89
|
-
// Calculate changes for each node
|
|
90
|
-
const change = Array(N).fill(0).map(() => [0, 0]);
|
|
91
55
|
|
|
56
|
+
// Create positions array
|
|
57
|
+
const positions: number[][] = nodes.map((node: Node) => [...pos[node]]);
|
|
58
|
+
|
|
59
|
+
// Initialize spring constant matrix
|
|
60
|
+
const N = nodes.length;
|
|
61
|
+
const K = Array(N)
|
|
62
|
+
.fill(0)
|
|
63
|
+
.map(() => Array(N).fill(1));
|
|
64
|
+
|
|
65
|
+
// Set diagonal to zero (no self-attraction)
|
|
92
66
|
for (let i = 0; i < N; i++) {
|
|
93
|
-
|
|
94
|
-
|
|
67
|
+
K[i][i] = 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Set stronger attraction between connected nodes
|
|
71
|
+
for (const [source, target] of edges) {
|
|
72
|
+
if (source === target) {continue;}
|
|
95
73
|
|
|
96
|
-
|
|
97
|
-
const
|
|
74
|
+
const i = nodeIndex[source];
|
|
75
|
+
const j = nodeIndex[target];
|
|
76
|
+
K[i][j] = a;
|
|
77
|
+
K[j][i] = a;
|
|
78
|
+
}
|
|
98
79
|
|
|
99
|
-
|
|
100
|
-
|
|
80
|
+
// Calculate rho (scale factor)
|
|
81
|
+
const rho = scaling * Math.sqrt(N);
|
|
101
82
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
83
|
+
// Optimization loop
|
|
84
|
+
const dt = 1e-3; // Time step
|
|
85
|
+
const etol = 1e-6; // Error tolerance
|
|
86
|
+
let error = etol + 1;
|
|
87
|
+
let nIter = 0;
|
|
88
|
+
|
|
89
|
+
while (error > etol && nIter < maxIter) {
|
|
90
|
+
// Calculate changes for each node
|
|
91
|
+
const change = Array(N)
|
|
92
|
+
.fill(0)
|
|
93
|
+
.map(() => [0, 0]);
|
|
94
|
+
|
|
95
|
+
for (let i = 0; i < N; i++) {
|
|
96
|
+
for (let j = 0; j < N; j++) {
|
|
97
|
+
if (i === j) {continue;}
|
|
98
|
+
|
|
99
|
+
// Calculate difference vector
|
|
100
|
+
const diff = positions[i].map((coord, dim) => coord - positions[j][dim]);
|
|
101
|
+
|
|
102
|
+
// Calculate distance (with minimum to avoid division by zero)
|
|
103
|
+
const dist = Math.sqrt(diff.reduce((sum, d) => sum + d * d, 0)) || 0.01;
|
|
104
|
+
|
|
105
|
+
// Calculate attractive and repulsive forces
|
|
106
|
+
for (let d = 0; d < diff.length; d++) {
|
|
107
|
+
change[i][d] += K[i][j] * diff[d] - (rho / dist) * diff[d];
|
|
108
|
+
}
|
|
109
|
+
}
|
|
105
110
|
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
111
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
// Update positions
|
|
113
|
+
for (let i = 0; i < N; i++) {
|
|
114
|
+
for (let d = 0; d < positions[i].length; d++) {
|
|
115
|
+
positions[i][d] += change[i][d] * dt;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
115
118
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
sum + Math.sqrt(c.reduce((s, v) => s + v * v, 0)), 0);
|
|
119
|
+
// Calculate error (sum of force magnitudes)
|
|
120
|
+
error = change.reduce((sum, c) => sum + Math.sqrt(c.reduce((s, v) => s + v * v, 0)), 0);
|
|
119
121
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
+
nIter++;
|
|
123
|
+
}
|
|
122
124
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
// Convert positions array back to object
|
|
126
|
+
const finalPos: PositionMap = {};
|
|
127
|
+
nodes.forEach((node: Node, i: number) => {
|
|
128
|
+
finalPos[node] = positions[i];
|
|
129
|
+
});
|
|
128
130
|
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
+
return finalPos;
|
|
132
|
+
}
|