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