@graphty/layout 1.2.0 → 1.2.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.
Files changed (92) hide show
  1. package/.github/workflows/ci.yml +3 -7
  2. package/CHANGELOG.md +14 -0
  3. package/README.md +1 -1
  4. package/dist/vitest.config.js +2 -2
  5. package/dist/vitest.config.js.map +1 -1
  6. package/examples/3d-kamada-kawai.html +4 -19
  7. package/examples/bfs-layout.html +26 -32
  8. package/examples/bipartite-layout.html +3 -3
  9. package/examples/forceatlas2-layout.html +106 -151
  10. package/{dist → examples}/layout-helpers.js +58 -30
  11. package/examples/multipartite-layout.html +32 -14
  12. package/examples/shell-layout.html +4 -2
  13. package/examples/spring-layout.html +1 -11
  14. package/package.json +3 -3
  15. package/src/algorithms/index.ts +6 -0
  16. package/src/algorithms/optimization/index.ts +12 -0
  17. package/src/algorithms/optimization/kamada-kawai-solver.ts +231 -0
  18. package/src/algorithms/optimization/lbfgs.ts +68 -0
  19. package/src/algorithms/optimization/line-search.ts +50 -0
  20. package/src/algorithms/optimization/types.ts +8 -0
  21. package/src/algorithms/planarity/check.ts +38 -0
  22. package/src/algorithms/planarity/embedding.ts +216 -0
  23. package/src/algorithms/planarity/index.ts +12 -0
  24. package/src/algorithms/planarity/lr-test.ts +70 -0
  25. package/src/algorithms/planarity/special-graphs.ts +126 -0
  26. package/src/generators/basic.ts +93 -0
  27. package/src/generators/bipartite.ts +45 -0
  28. package/src/generators/grid.ts +42 -0
  29. package/src/generators/index.ts +15 -0
  30. package/src/generators/random.ts +39 -0
  31. package/src/generators/scale-free.ts +72 -0
  32. package/src/index.ts +18 -0
  33. package/src/layouts/basic/index.ts +5 -0
  34. package/src/layouts/basic/random.ts +32 -0
  35. package/src/layouts/force-directed/arf.ts +130 -0
  36. package/src/layouts/force-directed/forceatlas2.ts +407 -0
  37. package/src/layouts/force-directed/fruchterman-reingold.ts +164 -0
  38. package/src/layouts/force-directed/index.ts +9 -0
  39. package/src/layouts/force-directed/kamada-kawai.ts +112 -0
  40. package/src/layouts/force-directed/spring.ts +35 -0
  41. package/src/layouts/geometric/circular.ts +77 -0
  42. package/src/layouts/geometric/index.ts +7 -0
  43. package/src/layouts/geometric/shell.ts +80 -0
  44. package/src/layouts/geometric/spiral.ts +93 -0
  45. package/src/layouts/hierarchical/bfs.ts +81 -0
  46. package/src/layouts/hierarchical/bipartite.ts +94 -0
  47. package/src/layouts/hierarchical/index.ts +7 -0
  48. package/src/layouts/hierarchical/multipartite.ts +88 -0
  49. package/src/layouts/index.ts +9 -0
  50. package/src/layouts/specialized/index.ts +6 -0
  51. package/src/layouts/specialized/planar.ts +65 -0
  52. package/src/layouts/specialized/spectral.ts +128 -0
  53. package/src/types/embedding.ts +11 -0
  54. package/src/types/graph.ts +13 -0
  55. package/src/types/index.ts +7 -0
  56. package/src/types/layout.ts +9 -0
  57. package/src/utils/graph.ts +77 -0
  58. package/src/utils/index.ts +9 -0
  59. package/src/utils/numpy.ts +111 -0
  60. package/src/utils/params.ts +26 -0
  61. package/src/utils/random.ts +53 -0
  62. package/src/utils/rescale.ts +137 -0
  63. package/test/arf-layout.test.ts +1 -1
  64. package/test/bfs-layout.test.ts +1 -1
  65. package/test/bipartite-layout.test.ts +1 -1
  66. package/test/circular-layout.test.ts +1 -1
  67. package/test/forceatlas2-layout.test.ts +1 -1
  68. package/test/fruchterman-reingold-layout.test.ts +1 -1
  69. package/test/graph-generators.test.ts +1 -1
  70. package/test/kamada-kawai-layout.test.ts +1 -1
  71. package/test/multipartite-layout.test.ts +1 -1
  72. package/test/planar-layout.test.ts +1 -1
  73. package/test/random-layout.test.ts +1 -1
  74. package/test/rescale-layout.test.ts +1 -1
  75. package/test/shell-layout.test.ts +1 -1
  76. package/test/spectral-layout.test.ts +1 -1
  77. package/test/spiral-layout.test.ts +1 -1
  78. package/test/spring-layout.test.ts +1 -1
  79. package/test/test-utils.ts +34 -0
  80. package/test/utils-graph.test.ts +155 -0
  81. package/test/utils-index.test.ts +77 -0
  82. package/test/utils-numpy.test.ts +272 -0
  83. package/test/utils-params.test.ts +99 -0
  84. package/test/utils-random.test.ts +229 -0
  85. package/vitest.config.ts +2 -2
  86. package/dist/layout-helpers.d.ts +0 -123
  87. package/dist/layout-helpers.js.map +0 -1
  88. package/dist/layout.d.ts +0 -275
  89. package/dist/layout.js +0 -2304
  90. package/dist/layout.js.map +0 -1
  91. package/layout-helpers.ts +0 -560
  92. package/layout.ts +0 -2893
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Embedding functions for planar graphs
3
+ */
4
+
5
+ import { Node, Edge, Embedding, PositionMap } from '../../types';
6
+
7
+ /**
8
+ * Create a triangulation-based embedding for a planar graph
9
+ *
10
+ * @param nodes - List of nodes
11
+ * @param edges - List of edges
12
+ * @returns Embedding object
13
+ */
14
+ export function createTriangulationEmbedding(
15
+ nodes: Node[],
16
+ edges: Edge[]
17
+ ): Embedding {
18
+ // Create a simple embedding using the incremental approach
19
+ const embedding: Embedding = {
20
+ nodeOrder: [...nodes],
21
+ faceList: [],
22
+ nodePositions: {}
23
+ };
24
+
25
+ // Create a map of adjacent nodes
26
+ const adjMap: Record<Node, Set<Node>> = {};
27
+ for (const node of nodes) {
28
+ adjMap[node] = new Set<Node>();
29
+ }
30
+
31
+ for (const [u, v] of edges) {
32
+ adjMap[u].add(v);
33
+ adjMap[v].add(u);
34
+ }
35
+
36
+ // Create outer face as a cycle (if possible)
37
+ const outerFace = findCycle(nodes, edges, adjMap) || nodes;
38
+ embedding.faceList.push(outerFace);
39
+
40
+ // Position nodes on a convex polygon (outer face)
41
+ const n = outerFace.length;
42
+ for (let i = 0; i < n; i++) {
43
+ const angle = 2 * Math.PI * i / n;
44
+ embedding.nodePositions[outerFace[i]] = [Math.cos(angle), Math.sin(angle)];
45
+ }
46
+
47
+ // Position interior nodes using barycentric coordinates
48
+ const interiorNodes = nodes.filter(node => !embedding.nodePositions[node]);
49
+
50
+ for (const node of interiorNodes) {
51
+ const neighbors = Array.from(adjMap[node]);
52
+
53
+ if (neighbors.length === 0) {
54
+ // Isolated node, place at center
55
+ embedding.nodePositions[node] = [0, 0];
56
+ } else {
57
+ // Average position of neighbors that have positions
58
+ let xSum = 0, ySum = 0, count = 0;
59
+
60
+ for (const neighbor of neighbors) {
61
+ if (embedding.nodePositions[neighbor]) {
62
+ xSum += embedding.nodePositions[neighbor][0];
63
+ ySum += embedding.nodePositions[neighbor][1];
64
+ count++;
65
+ }
66
+ }
67
+
68
+ if (count > 0) {
69
+ // Place slightly away from center to avoid overlaps
70
+ const jitter = 0.1 * Math.random();
71
+ embedding.nodePositions[node] = [
72
+ xSum / count + jitter * (Math.random() - 0.5),
73
+ ySum / count + jitter * (Math.random() - 0.5)
74
+ ];
75
+ } else {
76
+ // No neighbors have positions yet, place randomly inside unit circle
77
+ const r = 0.5 * Math.random();
78
+ const angle = 2 * Math.PI * Math.random();
79
+ embedding.nodePositions[node] = [r * Math.cos(angle), r * Math.sin(angle)];
80
+ }
81
+ }
82
+ }
83
+
84
+ return embedding;
85
+ }
86
+
87
+ /**
88
+ * Find a simple cycle in the graph (for outer face)
89
+ *
90
+ * @param nodes - List of nodes
91
+ * @param edges - List of edges
92
+ * @param adjMap - Adjacency map
93
+ * @returns Cycle as array of nodes, or null if none found
94
+ */
95
+ export function findCycle(
96
+ nodes: Node[],
97
+ edges: Edge[],
98
+ adjMap: Record<Node, Set<Node>>
99
+ ): Node[] | null {
100
+ if (nodes.length === 0) return null;
101
+ if (nodes.length <= 2) return nodes; // Not a real cycle but handle it
102
+
103
+ // Try to find a Hamiltonian cycle for simplicity (for small graphs)
104
+ if (nodes.length <= 8) {
105
+ const visited = new Set<Node>();
106
+ const path: Node[] = [];
107
+
108
+ function hamiltonianCycleDFS(node: Node): boolean {
109
+ path.push(node);
110
+ visited.add(node);
111
+
112
+ if (path.length === nodes.length) {
113
+ // Check if it's a cycle (last node connects to first)
114
+ if (adjMap[node].has(path[0])) {
115
+ return true;
116
+ }
117
+ // Not a cycle
118
+ visited.delete(node);
119
+ path.pop();
120
+ return false;
121
+ }
122
+
123
+ for (const neighbor of adjMap[node]) {
124
+ if (!visited.has(neighbor)) {
125
+ if (hamiltonianCycleDFS(neighbor)) {
126
+ return true;
127
+ }
128
+ }
129
+ }
130
+
131
+ visited.delete(node);
132
+ path.pop();
133
+ return false;
134
+ }
135
+
136
+ if (hamiltonianCycleDFS(nodes[0])) {
137
+ return path;
138
+ }
139
+ }
140
+
141
+ // Fallback: try to find any cycle using DFS
142
+ const visited = new Set<Node>();
143
+ const parent: Record<Node, Node | null> = {};
144
+ let cycleFound: Node[] | null = null;
145
+
146
+ function findCycleDFS(node: Node, parentNode: Node | null): boolean {
147
+ visited.add(node);
148
+
149
+ for (const neighbor of adjMap[node]) {
150
+ if (neighbor === parentNode) continue;
151
+
152
+ if (visited.has(neighbor)) {
153
+ // Found a cycle
154
+ cycleFound = constructCycle(node, neighbor, parent);
155
+ return true;
156
+ }
157
+
158
+ parent[neighbor] = node;
159
+ if (findCycleDFS(neighbor, node)) {
160
+ return true;
161
+ }
162
+ }
163
+
164
+ return false;
165
+ }
166
+
167
+ function constructCycle(u: Node, v: Node, parent: Record<Node, Node | null>): Node[] {
168
+ const cycle: Node[] = [v, u];
169
+ let current = u;
170
+
171
+ while (parent[current] !== undefined && parent[current] !== v) {
172
+ current = parent[current]!;
173
+ cycle.push(current);
174
+ }
175
+
176
+ return cycle;
177
+ }
178
+
179
+ // Try to find a cycle
180
+ for (const node of nodes) {
181
+ if (!visited.has(node)) {
182
+ parent[node] = null;
183
+ if (findCycleDFS(node, null)) {
184
+ break;
185
+ }
186
+ }
187
+ }
188
+
189
+ return cycleFound || nodes; // Fallback to all nodes if no cycle found
190
+ }
191
+
192
+ /**
193
+ * Convert a combinatorial embedding to node positions
194
+ *
195
+ * @param embedding - The embedding object
196
+ * @param nodes - List of nodes
197
+ * @returns Dictionary mapping nodes to positions
198
+ */
199
+ export function combinatorialEmbeddingToPos(
200
+ embedding: Embedding,
201
+ nodes: Node[]
202
+ ): PositionMap {
203
+ const pos: PositionMap = {};
204
+
205
+ // Use the positions from the embedding
206
+ for (const node of nodes) {
207
+ if (embedding.nodePositions[node]) {
208
+ pos[node] = embedding.nodePositions[node];
209
+ } else {
210
+ // Fallback for any nodes without positions
211
+ pos[node] = [0, 0];
212
+ }
213
+ }
214
+
215
+ return pos;
216
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Re-export all planarity algorithms
3
+ */
4
+
5
+ export { checkPlanarity } from './check';
6
+ export { isK5, isK33, tryFindBipartitePartition } from './special-graphs';
7
+ export { lrPlanarityTest } from './lr-test';
8
+ export {
9
+ createTriangulationEmbedding,
10
+ findCycle,
11
+ combinatorialEmbeddingToPos
12
+ } from './embedding';
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Left-Right Planarity Test implementation
3
+ */
4
+
5
+ import { Node, Edge, Embedding } from '../../types';
6
+ import { createTriangulationEmbedding } from './embedding';
7
+
8
+ /**
9
+ * Left-Right Planarity Test for general graphs
10
+ *
11
+ * @param nodes - List of nodes
12
+ * @param edges - List of edges
13
+ * @returns Object containing isPlanar flag and embedding
14
+ */
15
+ export function lrPlanarityTest(
16
+ nodes: Node[],
17
+ edges: Edge[]
18
+ ): { isPlanar: boolean; embedding: Embedding | null } {
19
+ // Create adjacency list for the graph
20
+ const adjList: Record<Node, Node[]> = {};
21
+ for (const node of nodes) {
22
+ adjList[node] = [];
23
+ }
24
+
25
+ for (const [u, v] of edges) {
26
+ adjList[u].push(v);
27
+ adjList[v].push(u);
28
+ }
29
+
30
+ // Step 1: Perform DFS to get an st-numbering (ordering of nodes)
31
+ const visited = new Set<Node>();
32
+ const ordering: Node[] = [];
33
+
34
+ function dfs(node: Node): void {
35
+ visited.add(node);
36
+ ordering.push(node);
37
+
38
+ for (const neighbor of adjList[node]) {
39
+ if (!visited.has(neighbor)) {
40
+ dfs(neighbor);
41
+ }
42
+ }
43
+ }
44
+
45
+ // Start DFS from first node
46
+ dfs(nodes[0]);
47
+
48
+ // If the graph is disconnected, it's still planar but we need to handle each component
49
+ if (ordering.length < nodes.length) {
50
+ // Create a simple triangulation embedding for disconnected graphs
51
+ return { isPlanar: true, embedding: createTriangulationEmbedding(nodes, edges) };
52
+ }
53
+
54
+ // Step 2: For a general implementation, we'll use a simplified approach
55
+ // since this would normally require implementing the entire LR algorithm
56
+
57
+ // For this implementation, since we can't fully implement Boyer-Myrvold,
58
+ // we'll create a reasonable planar embedding for most planar graphs
59
+
60
+ // We assume the graph is planar if it's sparse enough (|E| <= 3|V| - 6)
61
+ // This is a necessary but not sufficient condition for planar graphs
62
+ if (edges.length > 3 * nodes.length - 6) {
63
+ return { isPlanar: false, embedding: null };
64
+ }
65
+
66
+ // Create a planar embedding using a triangulation approach
67
+ const embedding = createTriangulationEmbedding(nodes, edges);
68
+
69
+ return { isPlanar: true, embedding };
70
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Special graph detection functions for planarity testing
3
+ */
4
+
5
+ import { Node, Edge } from '../../types';
6
+
7
+ /**
8
+ * Check if graph is K5 (complete graph with 5 nodes)
9
+ *
10
+ * @param nodes - List of nodes
11
+ * @param edges - List of edges
12
+ * @returns True if graph is K5
13
+ */
14
+ export function isK5(nodes: Node[], edges: Edge[]): boolean {
15
+ if (nodes.length !== 5) return false;
16
+
17
+ // K5 has exactly 10 edges
18
+ if (edges.length !== 10) return false;
19
+
20
+ // Check if every pair of distinct nodes is connected
21
+ for (let i = 0; i < nodes.length; i++) {
22
+ for (let j = i + 1; j < nodes.length; j++) {
23
+ const hasEdge = edges.some(
24
+ e => (e[0] === nodes[i] && e[1] === nodes[j]) ||
25
+ (e[0] === nodes[j] && e[1] === nodes[i])
26
+ );
27
+ if (!hasEdge) return false;
28
+ }
29
+ }
30
+
31
+ return true;
32
+ }
33
+
34
+ /**
35
+ * Check if graph is K3,3 (complete bipartite with 3,3 nodes)
36
+ *
37
+ * @param nodes - List of nodes
38
+ * @param edges - List of edges
39
+ * @returns True if graph is K3,3
40
+ */
41
+ export function isK33(nodes: Node[], edges: Edge[]): boolean {
42
+ if (nodes.length !== 6) return false;
43
+
44
+ // K3,3 has exactly 9 edges
45
+ if (edges.length !== 9) return false;
46
+
47
+ // Try to find a bipartite partition
48
+ const nodePartitions = tryFindBipartitePartition(nodes, edges);
49
+ if (!nodePartitions) return false;
50
+
51
+ const [part1, part2] = nodePartitions;
52
+
53
+ // Check if both partitions have size 3
54
+ if (part1.length !== 3 || part2.length !== 3) return false;
55
+
56
+ // Check if every node in part1 is connected to every node in part2
57
+ for (const n1 of part1) {
58
+ for (const n2 of part2) {
59
+ const hasEdge = edges.some(
60
+ e => (e[0] === n1 && e[1] === n2) ||
61
+ (e[0] === n2 && e[1] === n1)
62
+ );
63
+ if (!hasEdge) return false;
64
+ }
65
+ }
66
+
67
+ return true;
68
+ }
69
+
70
+ /**
71
+ * Try to find a bipartite partition of the nodes
72
+ *
73
+ * @param nodes - List of nodes
74
+ * @param edges - List of edges
75
+ * @returns Array of two partitions, or null if not bipartite
76
+ */
77
+ export function tryFindBipartitePartition(
78
+ nodes: Node[],
79
+ edges: Edge[]
80
+ ): [Node[], Node[]] | null {
81
+ const colorMap: Record<Node, number> = {};
82
+ const adjList: Record<Node, Node[]> = {};
83
+
84
+ // Create adjacency list
85
+ for (const node of nodes) {
86
+ adjList[node] = [];
87
+ }
88
+
89
+ for (const [u, v] of edges) {
90
+ adjList[u].push(v);
91
+ adjList[v].push(u);
92
+ }
93
+
94
+ // BFS to color nodes
95
+ const queue: Node[] = [nodes[0]];
96
+ colorMap[nodes[0]] = 0;
97
+
98
+ while (queue.length > 0) {
99
+ const node = queue.shift()!;
100
+ const nodeColor = colorMap[node];
101
+
102
+ for (const neighbor of adjList[node]) {
103
+ if (colorMap[neighbor] === undefined) {
104
+ colorMap[neighbor] = 1 - nodeColor; // Toggle color (0/1)
105
+ queue.push(neighbor);
106
+ } else if (colorMap[neighbor] === nodeColor) {
107
+ // Conflict: not bipartite
108
+ return null;
109
+ }
110
+ }
111
+ }
112
+
113
+ // Create partitions
114
+ const part0: Node[] = [];
115
+ const part1: Node[] = [];
116
+
117
+ for (const node of nodes) {
118
+ if (colorMap[node] === 0) {
119
+ part0.push(node);
120
+ } else {
121
+ part1.push(node);
122
+ }
123
+ }
124
+
125
+ return [part0, part1];
126
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Basic graph generation functions
3
+ */
4
+
5
+ import { Graph, Node, Edge } from '../types';
6
+
7
+ /**
8
+ * Create a complete graph with n nodes
9
+ * @param n - Number of nodes
10
+ * @returns Graph object with all nodes connected to all other nodes
11
+ */
12
+ export function completeGraph(n: number): Graph {
13
+ const nodes: Node[] = Array.from({ length: n }, (_, i) => i);
14
+ const edges: Edge[] = [];
15
+
16
+ for (let i = 0; i < n; i++) {
17
+ for (let j = i + 1; j < n; j++) {
18
+ edges.push([i, j]);
19
+ }
20
+ }
21
+
22
+ return {
23
+ nodes: () => nodes,
24
+ edges: () => edges
25
+ };
26
+ }
27
+
28
+ /**
29
+ * Create a cycle graph with n nodes
30
+ * @param n - Number of nodes
31
+ * @returns Graph object with nodes connected in a cycle
32
+ */
33
+ export function cycleGraph(n: number): Graph {
34
+ const nodes: Node[] = Array.from({ length: n }, (_, i) => i);
35
+ const edges: Edge[] = [];
36
+
37
+ for (let i = 0; i < n; i++) {
38
+ edges.push([i, (i + 1) % n]);
39
+ }
40
+
41
+ return {
42
+ nodes: () => nodes,
43
+ edges: () => edges
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Create a star graph with n nodes (1 center + n-1 leaves)
49
+ * @param n - Total number of nodes
50
+ * @returns Graph object with star topology
51
+ */
52
+ export function starGraph(n: number): Graph {
53
+ const nodes: Node[] = Array.from({ length: n }, (_, i) => i);
54
+ const edges: Edge[] = [];
55
+
56
+ // Connect all nodes to node 0 (center)
57
+ for (let i = 1; i < n; i++) {
58
+ edges.push([0, i]);
59
+ }
60
+
61
+ return {
62
+ nodes: () => nodes,
63
+ edges: () => edges
64
+ };
65
+ }
66
+
67
+ /**
68
+ * Create a wheel graph with n nodes (1 center + n-1 rim nodes)
69
+ * @param n - Total number of nodes
70
+ * @returns Graph object with wheel topology
71
+ */
72
+ export function wheelGraph(n: number): Graph {
73
+ const nodes: Node[] = Array.from({ length: n }, (_, i) => i);
74
+ const edges: Edge[] = [];
75
+
76
+ // Connect all rim nodes to center (node 0)
77
+ for (let i = 1; i < n; i++) {
78
+ edges.push([0, i]);
79
+ }
80
+
81
+ // Connect rim nodes in a cycle
82
+ for (let i = 1; i < n - 1; i++) {
83
+ edges.push([i, i + 1]);
84
+ }
85
+ if (n > 2) {
86
+ edges.push([n - 1, 1]);
87
+ }
88
+
89
+ return {
90
+ nodes: () => nodes,
91
+ edges: () => edges
92
+ };
93
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Bipartite graph generation function
3
+ */
4
+
5
+ import { Graph, Node, Edge } from '../types';
6
+
7
+ /**
8
+ * Create a bipartite graph with two sets of nodes
9
+ * @param n1 - Number of nodes in first set
10
+ * @param n2 - Number of nodes in second set
11
+ * @param p - Probability of edge between nodes in different sets
12
+ * @param seed - Random seed for reproducibility
13
+ * @returns Graph object with bipartite structure and setA/setB properties
14
+ */
15
+ export function bipartiteGraph(n1: number, n2: number, p: number, seed?: number): Graph & { setA: Node[], setB: Node[] } {
16
+ const setA: Node[] = Array.from({ length: n1 }, (_, i) => `A${i}`);
17
+ const setB: Node[] = Array.from({ length: n2 }, (_, i) => `B${i}`);
18
+ const nodes = [...setA, ...setB];
19
+ const edges: Edge[] = [];
20
+
21
+ // Simple deterministic pseudo-random if seed provided
22
+ let currentSeed = seed;
23
+ let random = seed !== undefined
24
+ ? () => {
25
+ currentSeed = (currentSeed! * 9301 + 49297) % 233280;
26
+ return currentSeed / 233280;
27
+ }
28
+ : Math.random;
29
+
30
+ // Only connect nodes between sets
31
+ for (const a of setA) {
32
+ for (const b of setB) {
33
+ if (random() < p) {
34
+ edges.push([a, b]);
35
+ }
36
+ }
37
+ }
38
+
39
+ return {
40
+ nodes: () => nodes,
41
+ edges: () => edges,
42
+ setA,
43
+ setB
44
+ };
45
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Grid graph generation function
3
+ */
4
+
5
+ import { Graph, Node, Edge } from '../types';
6
+
7
+ /**
8
+ * Create a grid graph with rows x cols nodes
9
+ * @param rows - Number of rows
10
+ * @param cols - Number of columns
11
+ * @returns Graph object with grid topology
12
+ */
13
+ export function gridGraph(rows: number, cols: number): Graph {
14
+ const nodes: Node[] = [];
15
+ const edges: Edge[] = [];
16
+
17
+ // Create nodes
18
+ for (let i = 0; i < rows; i++) {
19
+ for (let j = 0; j < cols; j++) {
20
+ nodes.push(`${i},${j}`);
21
+ }
22
+ }
23
+
24
+ // Create edges
25
+ for (let i = 0; i < rows; i++) {
26
+ for (let j = 0; j < cols; j++) {
27
+ // Connect to right neighbor
28
+ if (j < cols - 1) {
29
+ edges.push([`${i},${j}`, `${i},${j + 1}`]);
30
+ }
31
+ // Connect to bottom neighbor
32
+ if (i < rows - 1) {
33
+ edges.push([`${i},${j}`, `${i + 1},${j}`]);
34
+ }
35
+ }
36
+ }
37
+
38
+ return {
39
+ nodes: () => nodes,
40
+ edges: () => edges
41
+ };
42
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Re-export all graph generation functions
3
+ */
4
+
5
+ export {
6
+ completeGraph,
7
+ cycleGraph,
8
+ starGraph,
9
+ wheelGraph
10
+ } from './basic';
11
+
12
+ export { gridGraph } from './grid';
13
+ export { randomGraph } from './random';
14
+ export { bipartiteGraph } from './bipartite';
15
+ export { scaleFreeGraph } from './scale-free';
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Random graph generation function
3
+ */
4
+
5
+ import { Graph, Node, Edge } from '../types';
6
+
7
+ /**
8
+ * Create a random graph with n nodes and given edge probability
9
+ * @param n - Number of nodes
10
+ * @param p - Probability of edge between any two nodes (0-1)
11
+ * @param seed - Random seed for reproducibility
12
+ * @returns Graph object with random edges
13
+ */
14
+ export function randomGraph(n: number, p: number, seed?: number): Graph {
15
+ const nodes: Node[] = Array.from({ length: n }, (_, i) => i);
16
+ const edges: Edge[] = [];
17
+
18
+ // Simple deterministic pseudo-random if seed provided
19
+ let currentSeed = seed;
20
+ let random = seed !== undefined
21
+ ? () => {
22
+ currentSeed = (currentSeed! * 9301 + 49297) % 233280;
23
+ return currentSeed / 233280;
24
+ }
25
+ : Math.random;
26
+
27
+ for (let i = 0; i < n; i++) {
28
+ for (let j = i + 1; j < n; j++) {
29
+ if (random() < p) {
30
+ edges.push([i, j]);
31
+ }
32
+ }
33
+ }
34
+
35
+ return {
36
+ nodes: () => nodes,
37
+ edges: () => edges
38
+ };
39
+ }