@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,76 +2,82 @@
2
2
  * Circular layout algorithm
3
3
  */
4
4
 
5
- import { Graph, Node, PositionMap } from '../../types';
6
- import { _processParams } from '../../utils/params';
7
- import { getNodesFromGraph } from '../../utils/graph';
8
- import { RandomNumberGenerator } from '../../utils/random';
9
- import { np } from '../../utils/numpy';
5
+ import { Graph, Node, PositionMap } from "../../types";
6
+ import { getNodesFromGraph } from "../../utils/graph";
7
+ import { np } from "../../utils/numpy";
8
+ import { _processParams } from "../../utils/params";
9
+ import { RandomNumberGenerator } from "../../utils/random";
10
10
 
11
11
  /**
12
12
  * Position nodes on a circle (2D) or sphere (3D).
13
- *
14
13
  * @param G - Graph or list of nodes
15
14
  * @param scale - Scale factor for positions
16
15
  * @param center - Coordinate pair around which to center the layout
17
16
  * @param dim - Dimension of layout (supports 2D circle or 3D sphere)
18
17
  * @returns Positions dictionary keyed by node
19
18
  */
20
- export function circularLayout(G: Graph, scale: number = 1, center: number[] | null = null, dim: number = 2): PositionMap {
21
- if (dim < 2) {
22
- throw new Error("cannot handle dimensions < 2");
23
- }
19
+ export function circularLayout(
20
+ G: Graph,
21
+ scale: number = 1,
22
+ center: number[] | null = null,
23
+ dim: number = 2,
24
+ ): PositionMap {
25
+ if (dim < 2) {
26
+ throw new Error("cannot handle dimensions < 2");
27
+ }
24
28
 
25
- const processed = _processParams(G, center, dim);
26
- const nodes = getNodesFromGraph(processed.G);
27
- center = processed.center;
29
+ const processed = _processParams(G, center, dim);
30
+ const nodes = getNodesFromGraph(processed.G);
31
+ center = processed.center;
28
32
 
29
- const pos: PositionMap = {};
33
+ const pos: PositionMap = {};
30
34
 
31
- if (nodes.length === 0) {
32
- return pos;
33
- }
35
+ if (nodes.length === 0) {
36
+ return pos;
37
+ }
34
38
 
35
- if (nodes.length === 1) {
36
- pos[nodes[0]] = center;
37
- return pos;
38
- }
39
+ if (nodes.length === 1) {
40
+ pos[nodes[0]] = center;
41
+ return pos;
42
+ }
43
+
44
+ if (dim === 2) {
45
+ // 2D circle layout
46
+ const theta = np.linspace(0, 2 * Math.PI, nodes.length + 1).slice(0, -1);
39
47
 
40
- if (dim === 2) {
41
- // 2D circle layout
42
- const theta = np.linspace(0, 2 * Math.PI, nodes.length + 1).slice(0, -1);
48
+ nodes.forEach((node: Node, i: number) => {
49
+ const x: number = Math.cos(theta[i]) * scale + center[0];
50
+ const y: number = Math.sin(theta[i]) * scale + center[1];
51
+ pos[node] = [x, y];
52
+ });
53
+ } else if (dim === 3) {
54
+ // 3D sphere layout using Fibonacci spiral
55
+ const n = nodes.length;
56
+ const goldenRatio = (1 + Math.sqrt(5)) / 2;
43
57
 
44
- nodes.forEach((node: Node, i: number) => {
45
- const x: number = Math.cos(theta[i]) * scale + center[0];
46
- const y: number = Math.sin(theta[i]) * scale + center[1];
47
- pos[node] = [x, y];
48
- });
49
- } else if (dim === 3) {
50
- // 3D sphere layout using Fibonacci spiral
51
- const n = nodes.length;
52
- const goldenRatio = (1 + Math.sqrt(5)) / 2;
53
-
54
- nodes.forEach((node: Node, i: number) => {
55
- // Use Fibonacci spiral for even distribution on sphere
56
- const theta = 2 * Math.PI * i / goldenRatio;
57
- const phi = Math.acos(1 - 2 * (i + 0.5) / n);
58
-
59
- const x = Math.sin(phi) * Math.cos(theta) * scale + center[0];
60
- const y = Math.sin(phi) * Math.sin(theta) * scale + center[1];
61
- const z = Math.cos(phi) * scale + center[2];
62
-
63
- pos[node] = [x, y, z];
64
- });
65
- } else {
66
- // For higher dimensions, fall back to random on hypersphere
67
- const rng = new RandomNumberGenerator();
68
- nodes.forEach((node: Node) => {
69
- // Generate random point on unit hypersphere
70
- const coords = Array(dim).fill(0).map(() => rng.rand() as number * 2 - 1);
71
- const norm = Math.sqrt(coords.reduce((sum, c) => sum + c * c, 0));
72
- pos[node] = coords.map((c, j) => c / norm * scale + center[j]);
73
- });
74
- }
58
+ nodes.forEach((node: Node, i: number) => {
59
+ // Use Fibonacci spiral for even distribution on sphere
60
+ const theta = (2 * Math.PI * i) / goldenRatio;
61
+ const phi = Math.acos(1 - (2 * (i + 0.5)) / n);
75
62
 
76
- return pos;
77
- }
63
+ const x = Math.sin(phi) * Math.cos(theta) * scale + center[0];
64
+ const y = Math.sin(phi) * Math.sin(theta) * scale + center[1];
65
+ const z = Math.cos(phi) * scale + center[2];
66
+
67
+ pos[node] = [x, y, z];
68
+ });
69
+ } else {
70
+ // For higher dimensions, fall back to random on hypersphere
71
+ const rng = new RandomNumberGenerator();
72
+ nodes.forEach((node: Node) => {
73
+ // Generate random point on unit hypersphere
74
+ const coords = Array(dim)
75
+ .fill(0)
76
+ .map(() => (rng.rand() as number) * 2 - 1);
77
+ const norm = Math.sqrt(coords.reduce((sum, c) => sum + c * c, 0));
78
+ pos[node] = coords.map((c, j) => (c / norm) * scale + center[j]);
79
+ });
80
+ }
81
+
82
+ return pos;
83
+ }
@@ -2,6 +2,6 @@
2
2
  * Geometric layout algorithms
3
3
  */
4
4
 
5
- export { circularLayout } from './circular';
6
- export { shellLayout } from './shell';
7
- export { spiralLayout } from './spiral';
5
+ export { circularLayout } from "./circular";
6
+ export { shellLayout } from "./shell";
7
+ export { spiralLayout } from "./spiral";
@@ -2,14 +2,13 @@
2
2
  * Shell layout algorithm
3
3
  */
4
4
 
5
- import { Graph, Node, PositionMap } from '../../types';
6
- import { _processParams } from '../../utils/params';
7
- import { getNodesFromGraph } from '../../utils/graph';
8
- import { np } from '../../utils/numpy';
5
+ import { Graph, Node, PositionMap } from "../../types";
6
+ import { getNodesFromGraph } from "../../utils/graph";
7
+ import { np } from "../../utils/numpy";
8
+ import { _processParams } from "../../utils/params";
9
9
 
10
10
  /**
11
11
  * Position nodes in concentric circles.
12
- *
13
12
  * @param G - Graph or list of nodes
14
13
  * @param nlist - List of node lists for each shell
15
14
  * @param scale - Scale factor for positions
@@ -17,64 +16,70 @@ import { np } from '../../utils/numpy';
17
16
  * @param dim - Dimension of layout (currently only supports dim=2)
18
17
  * @returns Positions dictionary keyed by node
19
18
  */
20
- export function shellLayout(G: Graph, nlist: Node[][] | null = null, scale: number = 1, center: number[] | null = null, dim: number = 2): PositionMap {
21
- if (dim !== 2) {
22
- throw new Error("can only handle 2 dimensions");
23
- }
19
+ export function shellLayout(
20
+ G: Graph,
21
+ nlist: Node[][] | null = null,
22
+ scale: number = 1,
23
+ center: number[] | null = null,
24
+ dim: number = 2,
25
+ ): PositionMap {
26
+ if (dim !== 2) {
27
+ throw new Error("can only handle 2 dimensions");
28
+ }
24
29
 
25
- const processed = _processParams(G, center, dim);
26
- const nodes = getNodesFromGraph(processed.G);
27
- center = processed.center;
30
+ const processed = _processParams(G, center, dim);
31
+ const nodes = getNodesFromGraph(processed.G);
32
+ center = processed.center;
28
33
 
29
- const pos: PositionMap = {};
34
+ const pos: PositionMap = {};
30
35
 
31
- if (nodes.length === 0) {
32
- return pos;
33
- }
36
+ if (nodes.length === 0) {
37
+ return pos;
38
+ }
34
39
 
35
- if (nodes.length === 1) {
36
- pos[nodes[0]] = center;
37
- return pos;
38
- }
40
+ if (nodes.length === 1) {
41
+ pos[nodes[0]] = center;
42
+ return pos;
43
+ }
39
44
 
40
- // If no nlist is specified, put all nodes in a single shell
41
- if (!nlist) {
42
- nlist = [nodes];
43
- }
45
+ // If no nlist is specified, put all nodes in a single shell
46
+ if (!nlist) {
47
+ nlist = [nodes];
48
+ }
44
49
 
45
- const radiusBump = scale / nlist.length;
46
- let radius: number;
50
+ const radiusBump = scale / nlist.length;
51
+ let radius: number;
47
52
 
48
- if (nlist[0].length === 1) {
49
- // Single node at center
50
- radius = 0;
51
- pos[nlist[0][0]] = [...center];
52
- radius += radiusBump;
53
- } else {
54
- // Start at radius 1
55
- radius = radiusBump;
56
- }
53
+ if (nlist[0].length === 1) {
54
+ // Single node at center
55
+ radius = 0;
56
+ pos[nlist[0][0]] = [...center];
57
+ radius += radiusBump;
58
+ } else {
59
+ // Start at radius 1
60
+ radius = radiusBump;
61
+ }
57
62
 
58
- for (let i = 0; i < nlist.length; i++) {
59
- const shell = nlist[i];
60
- if (shell.length === 0) continue;
63
+ for (let i = 0; i < nlist.length; i++) {
64
+ const shell = nlist[i];
65
+ if (shell.length === 0) {continue;}
61
66
 
62
- if (shell.length === 1 && i === 0) {
63
- // Already handled the case of a single center node
64
- continue;
65
- }
67
+ if (shell.length === 1 && i === 0) {
68
+ // Already handled the case of a single center node
69
+ continue;
70
+ }
66
71
 
67
- // Calculate positions on a circle
68
- const theta = np.linspace(0, 2 * Math.PI, shell.length + 1).slice(0, -1);
72
+ // Calculate positions on a circle
73
+ const theta = np.linspace(0, 2 * Math.PI, shell.length + 1).slice(0, -1);
69
74
 
70
- shell.forEach((node: Node, j) => {
71
- const x = Math.cos(theta[j]) * radius + center[0];
72
- const y = Math.sin(theta[j]) * radius + center[1];
73
- pos[node] = [x, y];
74
- });
75
+ shell.forEach((node: Node, j) => {
76
+ const x = Math.cos(theta[j]) * radius + center[0];
77
+ const y = Math.sin(theta[j]) * radius + center[1];
78
+ pos[node] = [x, y];
79
+ });
75
80
 
76
- radius += radiusBump;
77
- }
81
+ radius += radiusBump;
82
+ }
78
83
 
79
- return pos;
80
- }
84
+ return pos;
85
+ }
@@ -2,14 +2,13 @@
2
2
  * Spiral layout algorithm
3
3
  */
4
4
 
5
- import { Graph, Node, PositionMap } from '../../types';
6
- import { _processParams } from '../../utils/params';
7
- import { getNodesFromGraph } from '../../utils/graph';
8
- import { rescaleLayout } from '../../utils/rescale';
5
+ import { Graph, Node, PositionMap } from "../../types";
6
+ import { getNodesFromGraph } from "../../utils/graph";
7
+ import { _processParams } from "../../utils/params";
8
+ import { rescaleLayout } from "../../utils/rescale";
9
9
 
10
10
  /**
11
11
  * Position nodes in a spiral layout.
12
- *
13
12
  * @param G - Graph or list of nodes
14
13
  * @param scale - Scale factor for positions
15
14
  * @param center - Coordinate pair around which to center the layout
@@ -19,75 +18,72 @@ import { rescaleLayout } from '../../utils/rescale';
19
18
  * @returns Positions dictionary keyed by node
20
19
  */
21
20
  export function spiralLayout(
22
- G: Graph,
23
- scale: number = 1,
24
- center: number[] | null = null,
25
- dim: number = 2,
26
- resolution: number = 0.35,
27
- equidistant: boolean = false
21
+ G: Graph,
22
+ scale: number = 1,
23
+ center: number[] | null = null,
24
+ dim: number = 2,
25
+ resolution: number = 0.35,
26
+ equidistant: boolean = false,
28
27
  ): PositionMap {
29
- if (dim !== 2) {
30
- throw new Error("can only handle 2 dimensions");
31
- }
28
+ if (dim !== 2) {
29
+ throw new Error("can only handle 2 dimensions");
30
+ }
32
31
 
33
- const processed = _processParams(G, center || [0, 0], dim);
34
- const nodes = getNodesFromGraph(processed.G);
35
- center = processed.center;
32
+ const processed = _processParams(G, center || [0, 0], dim);
33
+ const nodes = getNodesFromGraph(processed.G);
34
+ center = processed.center;
36
35
 
37
- const pos: PositionMap = {};
36
+ const pos: PositionMap = {};
38
37
 
39
- if (nodes.length === 0) {
40
- return pos;
41
- }
38
+ if (nodes.length === 0) {
39
+ return pos;
40
+ }
42
41
 
43
- if (nodes.length === 1) {
44
- pos[nodes[0]] = [...center];
45
- return pos;
46
- }
42
+ if (nodes.length === 1) {
43
+ pos[nodes[0]] = [...center];
44
+ return pos;
45
+ }
47
46
 
48
- let positions: number[][] = [];
47
+ let positions: number[][] = [];
49
48
 
50
- if (equidistant) {
51
- // Create equidistant points along the spiral
52
- // This matches the Python implementation logic
53
- const chord = 1;
54
- const step = 0.5;
55
- let theta = resolution;
56
- theta += chord / (step * theta);
49
+ if (equidistant) {
50
+ // Create equidistant points along the spiral
51
+ // This matches the Python implementation logic
52
+ const chord = 1;
53
+ const step = 0.5;
54
+ let theta = resolution;
55
+ theta += chord / (step * theta);
57
56
 
58
- for (let i = 0; i < nodes.length; i++) {
59
- const r = step * theta;
60
- theta += chord / r;
61
- positions.push([Math.cos(theta) * r, Math.sin(theta) * r]);
62
- }
63
- } else {
64
- // Create points with equal angle but increasing distance
65
- const dist = Array.from({ length: nodes.length }, (_, i) => parseFloat(String(i)));
66
- const angle = dist.map(d => resolution * d);
57
+ for (let i = 0; i < nodes.length; i++) {
58
+ const r = step * theta;
59
+ theta += chord / r;
60
+ positions.push([Math.cos(theta) * r, Math.sin(theta) * r]);
61
+ }
62
+ } else {
63
+ // Create points with equal angle but increasing distance
64
+ const dist = Array.from({ length: nodes.length }, (_, i) => parseFloat(String(i)));
65
+ const angle = dist.map((d) => resolution * d);
67
66
 
68
- positions = dist.map((d, i) => [
69
- Math.cos(angle[i]) * d,
70
- Math.sin(angle[i]) * d
71
- ]);
72
- }
67
+ positions = dist.map((d, i) => [Math.cos(angle[i]) * d, Math.sin(angle[i]) * d]);
68
+ }
73
69
 
74
- // Convert position array to position matrix for rescaling
75
- const posArray: number[][] = [];
76
- for (let i = 0; i < positions.length; i++) {
77
- posArray.push(positions[i]);
78
- }
70
+ // Convert position array to position matrix for rescaling
71
+ const posArray: number[][] = [];
72
+ for (let i = 0; i < positions.length; i++) {
73
+ posArray.push(positions[i]);
74
+ }
79
75
 
80
- // Rescale positions and add center offset
81
- const scaledPositions = rescaleLayout(posArray as any, scale) as any;
82
- for (let i = 0; i < scaledPositions.length; i++) {
83
- scaledPositions[i][0] += center[0];
84
- scaledPositions[i][1] += center[1];
85
- }
76
+ // Rescale positions and add center offset
77
+ const scaledPositions = rescaleLayout(posArray as any, scale) as any;
78
+ for (let i = 0; i < scaledPositions.length; i++) {
79
+ scaledPositions[i][0] += center[0];
80
+ scaledPositions[i][1] += center[1];
81
+ }
86
82
 
87
- // Create position dictionary
88
- for (let i = 0; i < nodes.length; i++) {
89
- pos[nodes[i]] = scaledPositions[i];
90
- }
83
+ // Create position dictionary
84
+ for (let i = 0; i < nodes.length; i++) {
85
+ pos[nodes[i]] = scaledPositions[i];
86
+ }
91
87
 
92
- return pos;
93
- }
88
+ return pos;
89
+ }
@@ -1,12 +1,11 @@
1
- import type { Graph, Node, Edge, PositionMap } from '../../types';
2
- import { getNodesFromGraph, getNeighbors } from '../../utils/graph';
3
- import { _processParams } from '../../utils/params';
4
- import { multipartiteLayout } from './multipartite';
1
+ import type { Edge, Graph, Node, PositionMap } from "../../types";
2
+ import { getNeighbors,getNodesFromGraph } from "../../utils/graph";
3
+ import { _processParams } from "../../utils/params";
4
+ import { multipartiteLayout } from "./multipartite";
5
5
 
6
6
  /**
7
7
  * Position nodes according to breadth-first search algorithm.
8
- *
9
- * @param G - Graph
8
+ * @param G - Graph
10
9
  * @param start - Starting node for bfs
11
10
  * @param align - The alignment of layers: 'vertical' or 'horizontal'
12
11
  * @param scale - Scale factor for positions
@@ -14,68 +13,68 @@ import { multipartiteLayout } from './multipartite';
14
13
  * @returns Positions dictionary keyed by node
15
14
  */
16
15
  export function bfsLayout(
17
- G: Graph,
18
- start: Node,
19
- align: 'vertical' | 'horizontal' = 'vertical',
20
- scale: number = 1,
21
- center: number[] | null = null
16
+ G: Graph,
17
+ start: Node,
18
+ align: "vertical" | "horizontal" = "vertical",
19
+ scale: number = 1,
20
+ center: number[] | null = null,
22
21
  ): PositionMap {
23
- const processed = _processParams(G, center || [0, 0], 2);
24
-
25
- // BFS layout requires a proper Graph, not just a list of nodes
26
- if (Array.isArray(processed.G)) {
27
- throw new Error('BFS layout requires a Graph with edges, not just a list of nodes');
28
- }
29
-
30
- const graph = processed.G;
31
- center = processed.center;
22
+ const processed = _processParams(G, center || [0, 0], 2);
32
23
 
33
- const allNodes = getNodesFromGraph(graph);
24
+ // BFS layout requires a proper Graph, not just a list of nodes
25
+ if (Array.isArray(processed.G)) {
26
+ throw new Error("BFS layout requires a Graph with edges, not just a list of nodes");
27
+ }
28
+
29
+ const graph = processed.G;
30
+ center = processed.center;
31
+
32
+ const allNodes = getNodesFromGraph(graph);
33
+
34
+ if (allNodes.length === 0) {
35
+ return {};
36
+ }
34
37
 
35
- if (allNodes.length === 0) {
36
- return {};
37
- }
38
+ // Compute BFS layers
39
+ const layers: Record<number, Node[]> = {};
40
+ const visited = new Set<Node>();
41
+ let currentLayer = 0;
38
42
 
39
- // Compute BFS layers
40
- const layers: Record<number, Node[]> = {};
41
- const visited = new Set<Node>();
42
- let currentLayer = 0;
43
+ // Starting layer
44
+ layers[currentLayer] = [start];
45
+ visited.add(start);
43
46
 
44
- // Starting layer
45
- layers[currentLayer] = [start];
46
- visited.add(start);
47
+ // BFS traversal
48
+ while (Object.values(layers).flat().length < allNodes.length) {
49
+ const nextLayer: Node[] = [];
50
+ const currentNodes = layers[currentLayer];
47
51
 
48
- // BFS traversal
49
- while (Object.values(layers).flat().length < allNodes.length) {
50
- const nextLayer: Node[] = [];
51
- const currentNodes = layers[currentLayer];
52
+ for (const node of currentNodes) {
53
+ // Get neighbors - this is a simplified approach
54
+ // In a real implementation, we would get neighbors from the graph
55
+ const neighbors = getNeighbors(graph, node);
52
56
 
53
- for (const node of currentNodes) {
54
- // Get neighbors - this is a simplified approach
55
- // In a real implementation, we would get neighbors from the graph
56
- const neighbors = getNeighbors(graph, node);
57
+ for (const neighbor of neighbors) {
58
+ if (!visited.has(neighbor)) {
59
+ nextLayer.push(neighbor);
60
+ visited.add(neighbor);
61
+ }
62
+ }
63
+ }
57
64
 
58
- for (const neighbor of neighbors) {
59
- if (!visited.has(neighbor)) {
60
- nextLayer.push(neighbor);
61
- visited.add(neighbor);
65
+ if (nextLayer.length === 0) {
66
+ // No more connected nodes
67
+ const unvisited: Node[] = allNodes.filter((node: Node) => !visited.has(node));
68
+ if (unvisited.length > 0) {
69
+ throw new Error("bfs_layout didn't include all nodes. Graph may be disconnected.");
70
+ }
71
+ break;
62
72
  }
63
- }
64
- }
65
73
 
66
- if (nextLayer.length === 0) {
67
- // No more connected nodes
68
- const unvisited: Node[] = allNodes.filter((node: Node) => !visited.has(node));
69
- if (unvisited.length > 0) {
70
- throw new Error("bfs_layout didn't include all nodes. Graph may be disconnected.");
71
- }
72
- break;
74
+ currentLayer++;
75
+ layers[currentLayer] = nextLayer;
73
76
  }
74
77
 
75
- currentLayer++;
76
- layers[currentLayer] = nextLayer;
77
- }
78
-
79
- // Use multipartite_layout to position the layers
80
- return multipartiteLayout(graph, layers, align, scale, center);
81
- }
78
+ // Use multipartite_layout to position the layers
79
+ return multipartiteLayout(graph, layers, align, scale, center);
80
+ }