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