@graphty/layout 1.3.2 → 1.5.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/README.md +7 -6
- package/dist/layout.js +41 -33
- package/dist/layout.js.map +1 -1
- package/dist/src/algorithms/index.d.ts +1 -0
- package/dist/src/algorithms/index.d.ts.map +1 -0
- package/dist/src/algorithms/optimization/index.d.ts +1 -0
- package/dist/src/algorithms/optimization/index.d.ts.map +1 -0
- package/dist/src/algorithms/optimization/kamada-kawai-solver.d.ts +2 -1
- package/dist/src/algorithms/optimization/kamada-kawai-solver.d.ts.map +1 -0
- package/dist/src/algorithms/optimization/kamada-kawai-solver.js.map +1 -1
- package/dist/src/algorithms/optimization/lbfgs.d.ts +3 -2
- package/dist/src/algorithms/optimization/lbfgs.d.ts.map +1 -0
- package/dist/src/algorithms/optimization/lbfgs.js +2 -2
- package/dist/src/algorithms/optimization/lbfgs.js.map +1 -1
- package/dist/src/algorithms/optimization/line-search.d.ts +1 -0
- package/dist/src/algorithms/optimization/line-search.d.ts.map +1 -0
- package/dist/src/algorithms/optimization/types.d.ts +1 -0
- package/dist/src/algorithms/optimization/types.d.ts.map +1 -0
- package/dist/src/algorithms/planarity/check.d.ts +3 -2
- package/dist/src/algorithms/planarity/check.d.ts.map +1 -0
- package/dist/src/algorithms/planarity/check.js +2 -2
- package/dist/src/algorithms/planarity/check.js.map +1 -1
- package/dist/src/algorithms/planarity/embedding.d.ts +3 -2
- package/dist/src/algorithms/planarity/embedding.d.ts.map +1 -0
- package/dist/src/algorithms/planarity/embedding.js +2 -2
- package/dist/src/algorithms/planarity/embedding.js.map +1 -1
- package/dist/src/algorithms/planarity/index.d.ts +1 -0
- package/dist/src/algorithms/planarity/index.d.ts.map +1 -0
- package/dist/src/algorithms/planarity/lr-test.d.ts +1 -0
- package/dist/src/algorithms/planarity/lr-test.d.ts.map +1 -0
- package/dist/src/algorithms/planarity/special-graphs.d.ts +1 -0
- package/dist/src/algorithms/planarity/special-graphs.d.ts.map +1 -0
- package/dist/src/algorithms/planarity/special-graphs.js.map +1 -1
- package/dist/src/generators/basic.d.ts +1 -0
- package/dist/src/generators/basic.d.ts.map +1 -0
- package/dist/src/generators/bipartite.d.ts +1 -0
- package/dist/src/generators/bipartite.d.ts.map +1 -0
- package/dist/src/generators/bipartite.js.map +1 -1
- package/dist/src/generators/grid.d.ts +1 -0
- package/dist/src/generators/grid.d.ts.map +1 -0
- package/dist/src/generators/index.d.ts +1 -0
- package/dist/src/generators/index.d.ts.map +1 -0
- package/dist/src/generators/random.d.ts +1 -0
- package/dist/src/generators/random.d.ts.map +1 -0
- package/dist/src/generators/random.js.map +1 -1
- package/dist/src/generators/scale-free.d.ts +1 -0
- package/dist/src/generators/scale-free.d.ts.map +1 -0
- package/dist/src/generators/scale-free.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/layouts/basic/index.d.ts +1 -0
- package/dist/src/layouts/basic/index.d.ts.map +1 -0
- package/dist/src/layouts/basic/random.d.ts +1 -0
- package/dist/src/layouts/basic/random.d.ts.map +1 -0
- package/dist/src/layouts/basic/random.js +1 -1
- package/dist/src/layouts/basic/random.js.map +1 -1
- package/dist/src/layouts/force-directed/arf.d.ts +1 -0
- package/dist/src/layouts/force-directed/arf.d.ts.map +1 -0
- package/dist/src/layouts/force-directed/arf.js.map +1 -1
- package/dist/src/layouts/force-directed/forceatlas2.d.ts +3 -2
- package/dist/src/layouts/force-directed/forceatlas2.d.ts.map +1 -0
- package/dist/src/layouts/force-directed/forceatlas2.js +31 -22
- package/dist/src/layouts/force-directed/forceatlas2.js.map +1 -1
- package/dist/src/layouts/force-directed/fruchterman-reingold.d.ts +2 -1
- package/dist/src/layouts/force-directed/fruchterman-reingold.d.ts.map +1 -0
- package/dist/src/layouts/force-directed/fruchterman-reingold.js +1 -1
- package/dist/src/layouts/force-directed/fruchterman-reingold.js.map +1 -1
- package/dist/src/layouts/force-directed/index.d.ts +1 -0
- package/dist/src/layouts/force-directed/index.d.ts.map +1 -0
- package/dist/src/layouts/force-directed/kamada-kawai.d.ts +2 -1
- package/dist/src/layouts/force-directed/kamada-kawai.d.ts.map +1 -0
- package/dist/src/layouts/force-directed/kamada-kawai.js +1 -1
- package/dist/src/layouts/force-directed/kamada-kawai.js.map +1 -1
- package/dist/src/layouts/force-directed/spring.d.ts +1 -0
- package/dist/src/layouts/force-directed/spring.d.ts.map +1 -0
- package/dist/src/layouts/geometric/circular.d.ts +1 -0
- package/dist/src/layouts/geometric/circular.d.ts.map +1 -0
- package/dist/src/layouts/geometric/circular.js +1 -1
- package/dist/src/layouts/geometric/circular.js.map +1 -1
- package/dist/src/layouts/geometric/index.d.ts +1 -0
- package/dist/src/layouts/geometric/index.d.ts.map +1 -0
- package/dist/src/layouts/geometric/shell.d.ts +1 -0
- package/dist/src/layouts/geometric/shell.d.ts.map +1 -0
- package/dist/src/layouts/geometric/shell.js +1 -1
- package/dist/src/layouts/geometric/shell.js.map +1 -1
- package/dist/src/layouts/geometric/spiral.d.ts +2 -1
- package/dist/src/layouts/geometric/spiral.d.ts.map +1 -0
- package/dist/src/layouts/geometric/spiral.js +1 -1
- package/dist/src/layouts/geometric/spiral.js.map +1 -1
- package/dist/src/layouts/hierarchical/bfs.d.ts +1 -0
- package/dist/src/layouts/hierarchical/bfs.d.ts.map +1 -0
- package/dist/src/layouts/hierarchical/bfs.js +1 -1
- package/dist/src/layouts/hierarchical/bfs.js.map +1 -1
- package/dist/src/layouts/hierarchical/bipartite.d.ts +1 -0
- package/dist/src/layouts/hierarchical/bipartite.d.ts.map +1 -0
- package/dist/src/layouts/hierarchical/bipartite.js +1 -1
- package/dist/src/layouts/hierarchical/bipartite.js.map +1 -1
- package/dist/src/layouts/hierarchical/index.d.ts +1 -0
- package/dist/src/layouts/hierarchical/index.d.ts.map +1 -0
- package/dist/src/layouts/hierarchical/multipartite.d.ts +1 -0
- package/dist/src/layouts/hierarchical/multipartite.d.ts.map +1 -0
- package/dist/src/layouts/hierarchical/multipartite.js +2 -2
- package/dist/src/layouts/hierarchical/multipartite.js.map +1 -1
- package/dist/src/layouts/index.d.ts +1 -0
- package/dist/src/layouts/index.d.ts.map +1 -0
- package/dist/src/layouts/specialized/index.d.ts +1 -0
- package/dist/src/layouts/specialized/index.d.ts.map +1 -0
- package/dist/src/layouts/specialized/planar.d.ts +2 -1
- package/dist/src/layouts/specialized/planar.d.ts.map +1 -0
- package/dist/src/layouts/specialized/planar.js +1 -1
- package/dist/src/layouts/specialized/planar.js.map +1 -1
- package/dist/src/layouts/specialized/spectral.d.ts +2 -1
- package/dist/src/layouts/specialized/spectral.d.ts.map +1 -0
- package/dist/src/layouts/specialized/spectral.js +1 -1
- package/dist/src/layouts/specialized/spectral.js.map +1 -1
- package/dist/src/types/embedding.d.ts +1 -0
- package/dist/src/types/embedding.d.ts.map +1 -0
- package/dist/src/types/graph.d.ts +2 -1
- package/dist/src/types/graph.d.ts.map +1 -0
- package/dist/src/types/index.d.ts +1 -0
- package/dist/src/types/index.d.ts.map +1 -0
- package/dist/src/types/layout.d.ts +1 -0
- package/dist/src/types/layout.d.ts.map +1 -0
- package/dist/src/utils/graph.d.ts +1 -0
- package/dist/src/utils/graph.d.ts.map +1 -0
- package/dist/src/utils/index.d.ts +1 -0
- package/dist/src/utils/index.d.ts.map +1 -0
- package/dist/src/utils/numpy.d.ts +5 -4
- package/dist/src/utils/numpy.d.ts.map +1 -0
- package/dist/src/utils/numpy.js.map +1 -1
- package/dist/src/utils/params.d.ts +1 -0
- package/dist/src/utils/params.d.ts.map +1 -0
- package/dist/src/utils/random.d.ts +1 -0
- package/dist/src/utils/random.d.ts.map +1 -0
- package/dist/src/utils/random.js.map +1 -1
- package/dist/src/utils/rescale.d.ts +1 -0
- package/dist/src/utils/rescale.d.ts.map +1 -0
- package/dist/src/utils/rescale.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +10 -14
- package/src/algorithms/optimization/kamada-kawai-solver.ts +6 -6
- package/src/algorithms/optimization/lbfgs.ts +2 -2
- package/src/algorithms/planarity/check.ts +2 -2
- package/src/algorithms/planarity/embedding.ts +3 -3
- package/src/algorithms/planarity/special-graphs.ts +1 -1
- package/src/generators/bipartite.ts +1 -1
- package/src/generators/random.ts +1 -1
- package/src/generators/scale-free.ts +3 -3
- package/src/layouts/basic/random.ts +1 -1
- package/src/layouts/force-directed/arf.ts +1 -1
- package/src/layouts/force-directed/forceatlas2.ts +58 -50
- package/src/layouts/force-directed/fruchterman-reingold.ts +2 -2
- package/src/layouts/force-directed/kamada-kawai.ts +2 -3
- package/src/layouts/geometric/circular.ts +1 -1
- package/src/layouts/geometric/shell.ts +1 -1
- package/src/layouts/geometric/spiral.ts +3 -3
- package/src/layouts/hierarchical/bfs.ts +2 -2
- package/src/layouts/hierarchical/bipartite.ts +1 -1
- package/src/layouts/hierarchical/multipartite.ts +3 -3
- package/src/layouts/specialized/planar.ts +2 -2
- package/src/layouts/specialized/spectral.ts +11 -11
- package/src/types/graph.ts +1 -1
- package/src/utils/numpy.ts +11 -11
- package/src/utils/random.ts +2 -2
- package/src/utils/rescale.ts +2 -2
- package/dist/vitest.config.d.ts +0 -2
- package/dist/vitest.config.js +0 -30
- package/dist/vitest.config.js.map +0 -1
|
@@ -10,14 +10,14 @@ import { isK5, isK33 } from "./special-graphs";
|
|
|
10
10
|
/**
|
|
11
11
|
* Check if graph is planar using a simplified version of Boyer-Myrvold algorithm.
|
|
12
12
|
* Returns planarity and embedding information.
|
|
13
|
-
* @param
|
|
13
|
+
* @param _G - Graph (reserved for future use)
|
|
14
14
|
* @param nodes - List of nodes
|
|
15
15
|
* @param edges - List of edges
|
|
16
16
|
* @param seed - Random seed for deterministic embedding generation
|
|
17
17
|
* @returns Object containing isPlanar flag and embedding
|
|
18
18
|
*/
|
|
19
19
|
export function checkPlanarity(
|
|
20
|
-
|
|
20
|
+
_G: Graph,
|
|
21
21
|
nodes: Node[],
|
|
22
22
|
edges: Edge[],
|
|
23
23
|
seed: number | null = null,
|
|
@@ -90,11 +90,11 @@ export function createTriangulationEmbedding(nodes: Node[], edges: Edge[], seed:
|
|
|
90
90
|
/**
|
|
91
91
|
* Find a simple cycle in the graph (for outer face)
|
|
92
92
|
* @param nodes - List of nodes
|
|
93
|
-
* @param
|
|
93
|
+
* @param _edges - List of edges (reserved for future use)
|
|
94
94
|
* @param adjMap - Adjacency map
|
|
95
95
|
* @returns Cycle as array of nodes, or null if none found
|
|
96
96
|
*/
|
|
97
|
-
export function findCycle(nodes: Node[],
|
|
97
|
+
export function findCycle(nodes: Node[], _edges: Edge[], adjMap: Record<Node, Set<Node>>): Node[] | null {
|
|
98
98
|
if (nodes.length === 0) {return null;}
|
|
99
99
|
if (nodes.length <= 2) {return nodes;} // Not a real cycle but handle it
|
|
100
100
|
|
|
@@ -167,7 +167,7 @@ export function findCycle(nodes: Node[], edges: Edge[], adjMap: Record<Node, Set
|
|
|
167
167
|
let current = u;
|
|
168
168
|
|
|
169
169
|
while (parent[current] !== undefined && parent[current] !== v) {
|
|
170
|
-
current = parent[current]
|
|
170
|
+
current = parent[current] as Node;
|
|
171
171
|
cycle.push(current);
|
|
172
172
|
}
|
|
173
173
|
|
|
@@ -86,7 +86,7 @@ export function tryFindBipartitePartition(nodes: Node[], edges: Edge[]): [Node[]
|
|
|
86
86
|
colorMap[nodes[0]] = 0;
|
|
87
87
|
|
|
88
88
|
while (queue.length > 0) {
|
|
89
|
-
const node = queue.shift()
|
|
89
|
+
const node = queue.shift() as Node;
|
|
90
90
|
const nodeColor = colorMap[node];
|
|
91
91
|
|
|
92
92
|
for (const neighbor of adjList[node]) {
|
|
@@ -28,7 +28,7 @@ export function bipartiteGraph(
|
|
|
28
28
|
const random =
|
|
29
29
|
seed !== undefined
|
|
30
30
|
? () => {
|
|
31
|
-
currentSeed = (currentSeed
|
|
31
|
+
currentSeed = ((currentSeed as number) * 9301 + 49297) % 233280;
|
|
32
32
|
return currentSeed / 233280;
|
|
33
33
|
}
|
|
34
34
|
: Math.random;
|
package/src/generators/random.ts
CHANGED
|
@@ -20,7 +20,7 @@ export function randomGraph(n: number, p: number, seed?: number): Graph {
|
|
|
20
20
|
const random =
|
|
21
21
|
seed !== undefined
|
|
22
22
|
? () => {
|
|
23
|
-
currentSeed = (currentSeed
|
|
23
|
+
currentSeed = ((currentSeed as number) * 9301 + 49297) % 233280;
|
|
24
24
|
return currentSeed / 233280;
|
|
25
25
|
}
|
|
26
26
|
: Math.random;
|
|
@@ -25,7 +25,7 @@ export function scaleFreeGraph(n: number, m: number, seed?: number): Graph {
|
|
|
25
25
|
const random =
|
|
26
26
|
seed !== undefined
|
|
27
27
|
? () => {
|
|
28
|
-
currentSeed = (currentSeed
|
|
28
|
+
currentSeed = ((currentSeed as number) * 9301 + 49297) % 233280;
|
|
29
29
|
return currentSeed / 233280;
|
|
30
30
|
}
|
|
31
31
|
: Math.random;
|
|
@@ -42,7 +42,7 @@ export function scaleFreeGraph(n: number, m: number, seed?: number): Graph {
|
|
|
42
42
|
// Add remaining nodes
|
|
43
43
|
for (let i = m + 1; i < n; i++) {
|
|
44
44
|
const targets = new Set<number>();
|
|
45
|
-
const totalDegree = degrees.reduce((sum, d) => sum + d, 0);
|
|
45
|
+
const totalDegree = degrees.reduce((sum: number, d: number) => sum + d, 0);
|
|
46
46
|
|
|
47
47
|
// Choose m targets based on preferential attachment
|
|
48
48
|
while (targets.size < m) {
|
|
@@ -50,7 +50,7 @@ export function scaleFreeGraph(n: number, m: number, seed?: number): Graph {
|
|
|
50
50
|
let cumSum = 0;
|
|
51
51
|
|
|
52
52
|
for (let j = 0; j < i; j++) {
|
|
53
|
-
cumSum += degrees[j];
|
|
53
|
+
cumSum += degrees[j] as number;
|
|
54
54
|
if (r <= cumSum && !targets.has(j)) {
|
|
55
55
|
targets.add(j);
|
|
56
56
|
break;
|
|
@@ -23,7 +23,7 @@ export function randomLayout(
|
|
|
23
23
|
): PositionMap {
|
|
24
24
|
const processed = _processParams(G, center, dim);
|
|
25
25
|
const nodes = getNodesFromGraph(processed.G);
|
|
26
|
-
center = processed
|
|
26
|
+
({ center } = processed);
|
|
27
27
|
|
|
28
28
|
const rng = new RandomNumberGenerator(seed ?? undefined);
|
|
29
29
|
const pos: PositionMap = {};
|
|
@@ -40,7 +40,7 @@ export function arfLayout(
|
|
|
40
40
|
const rng = new RandomNumberGenerator(seed ?? undefined);
|
|
41
41
|
const defaultPos: PositionMap = {};
|
|
42
42
|
nodes.forEach((node: Node) => {
|
|
43
|
-
if (!pos
|
|
43
|
+
if (!(pos as PositionMap)[node]) {
|
|
44
44
|
defaultPos[node] = [rng.rand() as number, rng.rand() as number];
|
|
45
45
|
}
|
|
46
46
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Edge, Graph, Node, PositionMap } from "../../types";
|
|
2
|
-
import {
|
|
2
|
+
import { getNodesFromGraph } from "../../utils/graph";
|
|
3
3
|
import { _processParams } from "../../utils/params";
|
|
4
4
|
import { RandomNumberGenerator } from "../../utils/random";
|
|
5
5
|
import { rescaleLayout } from "../../utils/rescale";
|
|
@@ -17,7 +17,7 @@ import { rescaleLayout } from "../../utils/rescale";
|
|
|
17
17
|
* @param nodeMass - Dictionary mapping nodes to their masses
|
|
18
18
|
* @param nodeSize - Dictionary mapping nodes to their sizes
|
|
19
19
|
* @param weight - Edge attribute for weight
|
|
20
|
-
* @param
|
|
20
|
+
* @param _dissuadeHubs - Whether to prevent hub nodes from clustering (unused)
|
|
21
21
|
* @param linlog - Whether to use logarithmic attraction
|
|
22
22
|
* @param seed - Random seed for initial positions
|
|
23
23
|
* @param dim - Dimension of layout
|
|
@@ -35,7 +35,7 @@ export function forceatlas2Layout(
|
|
|
35
35
|
nodeMass: Record<Node, number> | null = null,
|
|
36
36
|
nodeSize: Record<Node, number> | null = null,
|
|
37
37
|
weight: string | null = null,
|
|
38
|
-
|
|
38
|
+
_dissuadeHubs: boolean = false,
|
|
39
39
|
linlog: boolean = false,
|
|
40
40
|
seed: number | null = null,
|
|
41
41
|
dim: number = 2,
|
|
@@ -75,8 +75,8 @@ export function forceatlas2Layout(
|
|
|
75
75
|
}
|
|
76
76
|
} else {
|
|
77
77
|
// Some nodes don't have positions, initialize within the range of existing positions
|
|
78
|
-
const minPos = Array(dim).fill(Number.POSITIVE_INFINITY);
|
|
79
|
-
const maxPos = Array(dim).fill(Number.NEGATIVE_INFINITY);
|
|
78
|
+
const minPos: number[] = Array(dim).fill(Number.POSITIVE_INFINITY);
|
|
79
|
+
const maxPos: number[] = Array(dim).fill(Number.NEGATIVE_INFINITY);
|
|
80
80
|
|
|
81
81
|
// Find min and max of existing positions
|
|
82
82
|
for (const node in pos) {
|
|
@@ -125,17 +125,22 @@ export function forceatlas2Layout(
|
|
|
125
125
|
// Set node masses and sizes
|
|
126
126
|
for (let i = 0; i < nodes.length; i++) {
|
|
127
127
|
const node = nodes[i];
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
if (nodeMass && nodeMass[node]) {
|
|
129
|
+
mass[i] = nodeMass[node];
|
|
130
|
+
} else if (Array.isArray(graph)) {
|
|
131
|
+
mass[i] = 1;
|
|
132
|
+
} else {
|
|
133
|
+
mass[i] = getNodeDegree(graph, node) + 1;
|
|
134
|
+
}
|
|
130
135
|
|
|
131
136
|
size[i] = nodeSize && nodeSize[node] ? nodeSize[node] : 1;
|
|
132
137
|
}
|
|
133
138
|
|
|
134
139
|
// Create adjacency matrix
|
|
135
140
|
const n = nodes.length;
|
|
136
|
-
const A = Array(n)
|
|
141
|
+
const A: number[][] = Array(n)
|
|
137
142
|
.fill(0)
|
|
138
|
-
.map(() => Array(n).fill(0));
|
|
143
|
+
.map(() => Array(n).fill(0) as number[]);
|
|
139
144
|
|
|
140
145
|
// Populate adjacency matrix with edge weights
|
|
141
146
|
const edges = Array.isArray(graph) ? ([] as Edge[]) : graph.edges();
|
|
@@ -159,66 +164,69 @@ export function forceatlas2Layout(
|
|
|
159
164
|
}
|
|
160
165
|
|
|
161
166
|
// Initialize force arrays
|
|
162
|
-
const gravities = Array(n)
|
|
167
|
+
const gravities: number[][] = Array(n)
|
|
163
168
|
.fill(0)
|
|
164
|
-
.map(() => Array(dim).fill(0));
|
|
165
|
-
const attraction = Array(n)
|
|
169
|
+
.map(() => Array(dim).fill(0) as number[]);
|
|
170
|
+
const attraction: number[][] = Array(n)
|
|
166
171
|
.fill(0)
|
|
167
|
-
.map(() => Array(dim).fill(0));
|
|
168
|
-
const repulsion = Array(n)
|
|
172
|
+
.map(() => Array(dim).fill(0) as number[]);
|
|
173
|
+
const repulsion: number[][] = Array(n)
|
|
169
174
|
.fill(0)
|
|
170
|
-
.map(() => Array(dim).fill(0));
|
|
175
|
+
.map(() => Array(dim).fill(0) as number[]);
|
|
171
176
|
|
|
172
177
|
// Simulation parameters
|
|
173
178
|
let speed = 1;
|
|
174
179
|
let speedEfficiency = 1;
|
|
175
|
-
const
|
|
176
|
-
const
|
|
180
|
+
const _swing = 1; // Reserved for adaptive speed control
|
|
181
|
+
const _traction = 1; // Reserved for adaptive speed control
|
|
177
182
|
|
|
178
183
|
// Helper function to estimate factor for force scaling
|
|
179
184
|
function estimateFactor(
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
185
|
+
nodeCount: number,
|
|
186
|
+
swingValue: number,
|
|
187
|
+
tractionValue: number,
|
|
188
|
+
currentSpeed: number,
|
|
189
|
+
currentSpeedEfficiency: number,
|
|
190
|
+
jitterToleranceParam: number,
|
|
186
191
|
): [number, number] {
|
|
192
|
+
let resultSpeed = currentSpeed;
|
|
193
|
+
let resultSpeedEfficiency = currentSpeedEfficiency;
|
|
194
|
+
|
|
187
195
|
// Optimal jitter parameters
|
|
188
|
-
const optJitter = 0.05 * Math.sqrt(
|
|
196
|
+
const optJitter = 0.05 * Math.sqrt(nodeCount);
|
|
189
197
|
const minJitter = Math.sqrt(optJitter);
|
|
190
198
|
const maxJitter = 10;
|
|
191
199
|
const minSpeedEfficiency = 0.05;
|
|
192
200
|
|
|
193
201
|
// Estimate jitter based on current state
|
|
194
|
-
const other = Math.min(maxJitter, (optJitter *
|
|
195
|
-
let jitter =
|
|
202
|
+
const other = Math.min(maxJitter, (optJitter * tractionValue) / (nodeCount * nodeCount));
|
|
203
|
+
let jitter = jitterToleranceParam * Math.max(minJitter, other);
|
|
196
204
|
|
|
197
205
|
// Adjust speed efficiency based on swing/traction ratio
|
|
198
|
-
if (
|
|
199
|
-
if (
|
|
200
|
-
|
|
206
|
+
if (swingValue / tractionValue > 2.0) {
|
|
207
|
+
if (resultSpeedEfficiency > minSpeedEfficiency) {
|
|
208
|
+
resultSpeedEfficiency *= 0.5;
|
|
201
209
|
}
|
|
202
|
-
jitter = Math.max(jitter,
|
|
210
|
+
jitter = Math.max(jitter, jitterToleranceParam);
|
|
203
211
|
}
|
|
204
212
|
|
|
205
213
|
// Calculate target speed
|
|
206
|
-
const targetSpeed =
|
|
214
|
+
const targetSpeed = swingValue === 0 ? Number.POSITIVE_INFINITY : (jitter * resultSpeedEfficiency * tractionValue) / swingValue;
|
|
207
215
|
|
|
208
216
|
// Further adjust speed efficiency
|
|
209
|
-
if (
|
|
210
|
-
if (
|
|
211
|
-
|
|
217
|
+
if (swingValue > jitter * tractionValue) {
|
|
218
|
+
if (resultSpeedEfficiency > minSpeedEfficiency) {
|
|
219
|
+
resultSpeedEfficiency *= 0.7;
|
|
212
220
|
}
|
|
213
|
-
} else if (
|
|
214
|
-
|
|
221
|
+
} else if (resultSpeed < 1000) {
|
|
222
|
+
resultSpeedEfficiency *= 1.3;
|
|
215
223
|
}
|
|
216
224
|
|
|
217
225
|
// Limit the speed increase
|
|
218
226
|
const maxRise = 0.5;
|
|
219
|
-
|
|
227
|
+
resultSpeed = resultSpeed + Math.min(targetSpeed - resultSpeed, maxRise * resultSpeed);
|
|
220
228
|
|
|
221
|
-
return [
|
|
229
|
+
return [resultSpeed, resultSpeedEfficiency];
|
|
222
230
|
}
|
|
223
231
|
|
|
224
232
|
// Main simulation loop
|
|
@@ -233,17 +241,17 @@ export function forceatlas2Layout(
|
|
|
233
241
|
}
|
|
234
242
|
|
|
235
243
|
// Compute pairwise differences and distances
|
|
236
|
-
const diff = Array(n)
|
|
244
|
+
const diff: number[][][] = Array(n)
|
|
237
245
|
.fill(0)
|
|
238
246
|
.map(() =>
|
|
239
247
|
Array(n)
|
|
240
248
|
.fill(0)
|
|
241
|
-
.map(() => Array(dim).fill(0)),
|
|
249
|
+
.map(() => Array(dim).fill(0) as number[]),
|
|
242
250
|
);
|
|
243
251
|
|
|
244
|
-
const distance = Array(n)
|
|
252
|
+
const distance: number[][] = Array(n)
|
|
245
253
|
.fill(0)
|
|
246
|
-
.map(() => Array(n).fill(0));
|
|
254
|
+
.map(() => Array(n).fill(0) as number[]);
|
|
247
255
|
|
|
248
256
|
for (let i = 0; i < n; i++) {
|
|
249
257
|
for (let j = 0; j < n; j++) {
|
|
@@ -253,7 +261,7 @@ export function forceatlas2Layout(
|
|
|
253
261
|
diff[i][j][d] = posArray[i][d] - posArray[j][d];
|
|
254
262
|
}
|
|
255
263
|
|
|
256
|
-
distance[i][j] = Math.sqrt(diff[i][j].reduce((sum, d) => sum + d * d, 0));
|
|
264
|
+
distance[i][j] = Math.sqrt(diff[i][j].reduce((sum: number, d: number) => sum + d * d, 0));
|
|
257
265
|
// Prevent division by zero
|
|
258
266
|
if (distance[i][j] < 0.01) {distance[i][j] = 0.01;}
|
|
259
267
|
}
|
|
@@ -324,7 +332,7 @@ export function forceatlas2Layout(
|
|
|
324
332
|
|
|
325
333
|
// Calculate gravity forces
|
|
326
334
|
// First find the center of mass
|
|
327
|
-
const centerOfMass = Array(dim).fill(0);
|
|
335
|
+
const centerOfMass: number[] = Array(dim).fill(0);
|
|
328
336
|
for (let i = 0; i < n; i++) {
|
|
329
337
|
for (let d = 0; d < dim; d++) {
|
|
330
338
|
centerOfMass[d] += posArray[i][d] / n;
|
|
@@ -344,7 +352,7 @@ export function forceatlas2Layout(
|
|
|
344
352
|
}
|
|
345
353
|
} else {
|
|
346
354
|
// Regular gravity model
|
|
347
|
-
const dist = Math.sqrt(posCentered.reduce((sum, val) => sum + val * val, 0));
|
|
355
|
+
const dist = Math.sqrt(posCentered.reduce((sum: number, val: number) => sum + val * val, 0));
|
|
348
356
|
|
|
349
357
|
if (dist > 0.01) {
|
|
350
358
|
for (let d = 0; d < dim; d++) {
|
|
@@ -369,13 +377,13 @@ export function forceatlas2Layout(
|
|
|
369
377
|
|
|
370
378
|
// Calculate swing and traction for this node
|
|
371
379
|
const oldPos = [...posArray[i]];
|
|
372
|
-
const newPos = oldPos.map((p, d) => p + update[i][d]);
|
|
380
|
+
const newPos = oldPos.map((p, d) => p + (update[i][d] as number));
|
|
373
381
|
|
|
374
382
|
const swingVector = oldPos.map((p, d) => p - newPos[d]);
|
|
375
383
|
const tractionVector = oldPos.map((p, d) => p + newPos[d]);
|
|
376
384
|
|
|
377
|
-
const swingMagnitude = Math.sqrt(swingVector.reduce((sum, val) => sum + val * val, 0));
|
|
378
|
-
const tractionMagnitude = Math.sqrt(tractionVector.reduce((sum, val) => sum + val * val, 0));
|
|
385
|
+
const swingMagnitude = Math.sqrt(swingVector.reduce((sum: number, val: number) => sum + val * val, 0));
|
|
386
|
+
const tractionMagnitude = Math.sqrt(tractionVector.reduce((sum: number, val: number) => sum + val * val, 0));
|
|
379
387
|
|
|
380
388
|
totalSwing += mass[i] * swingMagnitude;
|
|
381
389
|
totalTraction += 0.5 * mass[i] * tractionMagnitude;
|
|
@@ -399,7 +407,7 @@ export function forceatlas2Layout(
|
|
|
399
407
|
|
|
400
408
|
if (adjustSizes) {
|
|
401
409
|
// Calculate displacement magnitude
|
|
402
|
-
const df = Math.sqrt(update[i].reduce((sum, val) => sum + val * val, 0));
|
|
410
|
+
const df = Math.sqrt(update[i].reduce((sum: number, val: number) => sum + val * val, 0));
|
|
403
411
|
const swinging = mass[i] * df;
|
|
404
412
|
|
|
405
413
|
// Determine scaling factor with size adjustments
|
|
@@ -407,7 +415,7 @@ export function forceatlas2Layout(
|
|
|
407
415
|
factor = Math.min(factor * df, 10) / df;
|
|
408
416
|
} else {
|
|
409
417
|
// Standard scaling factor
|
|
410
|
-
const swinging = mass[i] * Math.sqrt(update[i].reduce((sum, val) => sum + val * val, 0));
|
|
418
|
+
const swinging = (mass[i] as number) * Math.sqrt(update[i].reduce((sum: number, val: number) => sum + val * val, 0));
|
|
411
419
|
factor = speed / (1 + Math.sqrt(speed * swinging));
|
|
412
420
|
}
|
|
413
421
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Fruchterman-Reingold force-directed layout algorithm
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import type { Graph, Node, PositionMap } from "../../types";
|
|
6
6
|
import { getEdgesFromGraph,getNodesFromGraph } from "../../utils/graph";
|
|
7
7
|
import { _processParams } from "../../utils/params";
|
|
8
8
|
import { RandomNumberGenerator } from "../../utils/random";
|
|
@@ -34,7 +34,7 @@ export function fruchtermanReingoldLayout(
|
|
|
34
34
|
): PositionMap {
|
|
35
35
|
const processed = _processParams(G, center, dim);
|
|
36
36
|
const graph = processed.G;
|
|
37
|
-
center = processed
|
|
37
|
+
({ center } = processed);
|
|
38
38
|
|
|
39
39
|
const nodes = getNodesFromGraph(graph);
|
|
40
40
|
const edges = getEdgesFromGraph(graph);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import { _computeShortestPathDistances, _kamadaKawaiSolve } from "../../algorithms/optimization";
|
|
1
|
+
import { _computeShortestPathDistances, _kamadaKawaiSolve, type DistanceMap } from "../../algorithms/optimization";
|
|
3
2
|
import type { Graph, Node, PositionMap } from "../../types";
|
|
4
3
|
import { getNodesFromGraph } from "../../utils/graph";
|
|
5
4
|
import { _processParams } from "../../utils/params";
|
|
@@ -28,7 +27,7 @@ export function kamadaKawaiLayout(
|
|
|
28
27
|
): PositionMap {
|
|
29
28
|
const processed = _processParams(G, center, dim);
|
|
30
29
|
const graph = processed.G;
|
|
31
|
-
center = processed
|
|
30
|
+
({ center } = processed);
|
|
32
31
|
|
|
33
32
|
const nodes = getNodesFromGraph(graph);
|
|
34
33
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Spiral layout algorithm
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { Graph,
|
|
5
|
+
import type { Graph, PositionMap } from "../../types";
|
|
6
6
|
import { getNodesFromGraph } from "../../utils/graph";
|
|
7
7
|
import { _processParams } from "../../utils/params";
|
|
8
8
|
import { rescaleLayout } from "../../utils/rescale";
|
|
@@ -31,7 +31,7 @@ export function spiralLayout(
|
|
|
31
31
|
|
|
32
32
|
const processed = _processParams(G, center || [0, 0], dim);
|
|
33
33
|
const nodes = getNodesFromGraph(processed.G);
|
|
34
|
-
center = processed
|
|
34
|
+
({ center } = processed);
|
|
35
35
|
|
|
36
36
|
const pos: PositionMap = {};
|
|
37
37
|
|
|
@@ -74,7 +74,7 @@ export function spiralLayout(
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
// Rescale positions and add center offset
|
|
77
|
-
const scaledPositions = rescaleLayout(posArray
|
|
77
|
+
const scaledPositions = rescaleLayout(posArray, scale) as number[][];
|
|
78
78
|
for (let i = 0; i < scaledPositions.length; i++) {
|
|
79
79
|
scaledPositions[i][0] += center[0];
|
|
80
80
|
scaledPositions[i][1] += center[1];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Graph, Node, PositionMap } from "../../types";
|
|
2
2
|
import { getNeighbors,getNodesFromGraph } from "../../utils/graph";
|
|
3
3
|
import { _processParams } from "../../utils/params";
|
|
4
4
|
import { multipartiteLayout } from "./multipartite";
|
|
@@ -27,7 +27,7 @@ export function bfsLayout(
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const graph = processed.G;
|
|
30
|
-
center = processed
|
|
30
|
+
({ center } = processed);
|
|
31
31
|
|
|
32
32
|
const allNodes = getNodesFromGraph(graph);
|
|
33
33
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Graph, Node, PositionMap } from "../../types";
|
|
2
|
-
import {
|
|
2
|
+
import { getNodesFromGraph } from "../../utils/graph";
|
|
3
3
|
import { _processParams } from "../../utils/params";
|
|
4
4
|
import { rescaleLayout } from "../../utils/rescale";
|
|
5
5
|
|
|
@@ -25,7 +25,7 @@ export function multipartiteLayout(
|
|
|
25
25
|
|
|
26
26
|
const processed = _processParams(G, center || [0, 0], 2);
|
|
27
27
|
const graph = processed.G;
|
|
28
|
-
center = processed
|
|
28
|
+
({ center } = processed);
|
|
29
29
|
|
|
30
30
|
const allNodes = getNodesFromGraph(graph);
|
|
31
31
|
|
|
@@ -58,7 +58,7 @@ export function multipartiteLayout(
|
|
|
58
58
|
let pos: PositionMap = {};
|
|
59
59
|
|
|
60
60
|
// Process each layer
|
|
61
|
-
Object.entries(layers).forEach(([
|
|
61
|
+
Object.entries(layers).forEach(([_layer, nodes], layerIdx) => {
|
|
62
62
|
const layerNodes = Array.isArray(nodes) ? nodes : [nodes];
|
|
63
63
|
const layerSize = layerNodes.length;
|
|
64
64
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { checkPlanarity, combinatorialEmbeddingToPos } from "../../algorithms/planarity";
|
|
6
|
-
import {
|
|
6
|
+
import type { Graph, PositionMap } from "../../types";
|
|
7
7
|
import { getEdgesFromGraph,getNodesFromGraph } from "../../utils/graph";
|
|
8
8
|
import { _processParams } from "../../utils/params";
|
|
9
9
|
import { rescaleLayout } from "../../utils/rescale";
|
|
@@ -36,7 +36,7 @@ export function planarLayout(
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
const graph = processed.G;
|
|
39
|
-
center = processed
|
|
39
|
+
({ center } = processed);
|
|
40
40
|
|
|
41
41
|
const nodes = getNodesFromGraph(graph);
|
|
42
42
|
const edges = getEdgesFromGraph(graph);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Spectral layout algorithm using eigenvectors of the graph Laplacian
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import type { Graph, Node, PositionMap } from "../../types";
|
|
6
6
|
import { getEdgesFromGraph,getNodesFromGraph } from "../../utils/graph";
|
|
7
7
|
import { _processParams } from "../../utils/params";
|
|
8
8
|
import { rescaleLayout } from "../../utils/rescale";
|
|
@@ -23,7 +23,7 @@ export function spectralLayout(
|
|
|
23
23
|
): PositionMap {
|
|
24
24
|
const processed = _processParams(G, center, dim);
|
|
25
25
|
const graph = processed.G;
|
|
26
|
-
center = processed
|
|
26
|
+
({ center } = processed);
|
|
27
27
|
|
|
28
28
|
const nodes = getNodesFromGraph(graph);
|
|
29
29
|
|
|
@@ -47,9 +47,9 @@ export function spectralLayout(
|
|
|
47
47
|
nodeIndices[node] = i;
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
-
const A = Array(N)
|
|
50
|
+
const A: number[][] = Array(N)
|
|
51
51
|
.fill(0)
|
|
52
|
-
.map(() => Array(N).fill(0));
|
|
52
|
+
.map(() => Array(N).fill(0) as number[]);
|
|
53
53
|
const edges = getEdgesFromGraph(graph);
|
|
54
54
|
|
|
55
55
|
for (const [source, target] of edges) {
|
|
@@ -60,12 +60,12 @@ export function spectralLayout(
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
// Create Laplacian matrix: L = D - A where D is degree matrix
|
|
63
|
-
const L = Array(N)
|
|
63
|
+
const L: number[][] = Array(N)
|
|
64
64
|
.fill(0)
|
|
65
|
-
.map(() => Array(N).fill(0));
|
|
65
|
+
.map(() => Array(N).fill(0) as number[]);
|
|
66
66
|
for (let i = 0; i < N; i++) {
|
|
67
67
|
// Compute degree (sum of row)
|
|
68
|
-
L[i][i] = A[i].reduce((sum, val) => sum + val, 0);
|
|
68
|
+
L[i][i] = A[i].reduce((sum: number, val: number) => sum + val, 0);
|
|
69
69
|
for (let j = 0; j < N; j++) {
|
|
70
70
|
L[i][j] -= A[i][j];
|
|
71
71
|
}
|
|
@@ -95,7 +95,7 @@ export function spectralLayout(
|
|
|
95
95
|
// This is a simplification of the actual algorithm
|
|
96
96
|
for (let iter = 0; iter < 100; iter++) {
|
|
97
97
|
// Apply Laplacian
|
|
98
|
-
const newVec = Array(N).fill(0);
|
|
98
|
+
const newVec: number[] = Array(N).fill(0);
|
|
99
99
|
for (let i = 0; i < N; i++) {
|
|
100
100
|
for (let j = 0; j < N; j++) {
|
|
101
101
|
newVec[i] += L[i][j] * vector[j];
|
|
@@ -103,13 +103,13 @@ export function spectralLayout(
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
// Orthogonalize against the constant vector (eigenvector with eigenvalue 0)
|
|
106
|
-
const mean = newVec.reduce((acc, val) => acc + val, 0) / N;
|
|
106
|
+
const mean = newVec.reduce((acc: number, val: number) => acc + val, 0) / N;
|
|
107
107
|
newVec.forEach((val, idx, arr) => {
|
|
108
108
|
arr[idx] = val - mean;
|
|
109
109
|
});
|
|
110
110
|
|
|
111
111
|
// Normalize
|
|
112
|
-
const newNorm = Math.sqrt(newVec.reduce((acc, val) => acc + val * val, 0));
|
|
112
|
+
const newNorm = Math.sqrt(newVec.reduce((acc: number, val: number) => acc + val * val, 0));
|
|
113
113
|
if (newNorm < 1e-10) {continue;} // Skip if vector is close to zero
|
|
114
114
|
|
|
115
115
|
vector = newVec.map((val) => val / newNorm);
|
|
@@ -129,7 +129,7 @@ export function spectralLayout(
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
// Rescale and create position dictionary
|
|
132
|
-
const scaledPositions = rescaleLayout(positions
|
|
132
|
+
const scaledPositions = rescaleLayout(positions, scale);
|
|
133
133
|
const pos: PositionMap = {};
|
|
134
134
|
nodes.forEach((node: Node, i: number) => {
|
|
135
135
|
pos[node] = (scaledPositions as number[][])[i].map((val: number, j: number) => val + center[j]);
|
package/src/types/graph.ts
CHANGED
package/src/utils/numpy.ts
CHANGED
|
@@ -4,28 +4,28 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
export const np = {
|
|
7
|
-
zeros: function (shape: number | number[]): number
|
|
7
|
+
zeros: function (shape: number | number[]): number[] | number[][] {
|
|
8
8
|
if (typeof shape === "number") {
|
|
9
|
-
return Array(shape).fill(0);
|
|
9
|
+
return Array(shape).fill(0) as number[];
|
|
10
10
|
}
|
|
11
11
|
if (shape.length === 1) {
|
|
12
|
-
return Array(shape[0]).fill(0);
|
|
12
|
+
return Array(shape[0]).fill(0) as number[];
|
|
13
13
|
}
|
|
14
14
|
return Array(shape[0])
|
|
15
15
|
.fill(0)
|
|
16
|
-
.map(() => this.zeros(shape.slice(1)));
|
|
16
|
+
.map(() => this.zeros(shape.slice(1))) as number[][];
|
|
17
17
|
},
|
|
18
18
|
|
|
19
|
-
ones: function (shape: number | number[]): number |
|
|
19
|
+
ones: function (shape: number | number[]): number[] | number[][] {
|
|
20
20
|
if (typeof shape === "number") {
|
|
21
|
-
return Array(shape).fill(1);
|
|
21
|
+
return Array(shape).fill(1) as number[];
|
|
22
22
|
}
|
|
23
23
|
if (shape.length === 1) {
|
|
24
|
-
return Array(shape[0]).fill(1);
|
|
24
|
+
return Array(shape[0]).fill(1) as number[];
|
|
25
25
|
}
|
|
26
26
|
return Array(shape[0])
|
|
27
27
|
.fill(1)
|
|
28
|
-
.map(() => this.ones(shape.slice(1)));
|
|
28
|
+
.map(() => this.ones(shape.slice(1))) as number[][];
|
|
29
29
|
},
|
|
30
30
|
|
|
31
31
|
linspace: function (start: number, stop: number, num: number): number[] {
|
|
@@ -36,12 +36,12 @@ export const np = {
|
|
|
36
36
|
return Array.from({ length: num }, (_, i) => start + i * step);
|
|
37
37
|
},
|
|
38
38
|
|
|
39
|
-
array: function (arr:
|
|
39
|
+
array: function (arr: number | number[]): number[] {
|
|
40
40
|
return Array.isArray(arr) ? [...arr] : [arr];
|
|
41
41
|
},
|
|
42
42
|
|
|
43
|
-
repeat: function (a:
|
|
44
|
-
const result:
|
|
43
|
+
repeat: function (a: number | number[], repeats: number): number[] {
|
|
44
|
+
const result: number[] = [];
|
|
45
45
|
for (let i = 0; i < repeats; i++) {
|
|
46
46
|
result.push(...np.array(a));
|
|
47
47
|
}
|