@graphty/layout 1.2.3 → 1.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/algorithms/index.d.ts +5 -0
- package/dist/src/algorithms/index.js +6 -0
- package/dist/src/algorithms/index.js.map +1 -0
- package/dist/src/algorithms/optimization/index.d.ts +7 -0
- package/dist/src/algorithms/optimization/index.js +7 -0
- package/dist/src/algorithms/optimization/index.js.map +1 -0
- package/dist/src/algorithms/optimization/kamada-kawai-solver.d.ts +32 -0
- package/dist/src/algorithms/optimization/kamada-kawai-solver.js +176 -0
- package/dist/src/algorithms/optimization/kamada-kawai-solver.js.map +1 -0
- package/dist/src/algorithms/optimization/lbfgs.d.ts +13 -0
- package/dist/src/algorithms/optimization/lbfgs.js +56 -0
- package/dist/src/algorithms/optimization/lbfgs.js.map +1 -0
- package/dist/src/algorithms/optimization/line-search.d.ts +15 -0
- package/dist/src/algorithms/optimization/line-search.js +37 -0
- package/dist/src/algorithms/optimization/line-search.js.map +1 -0
- package/dist/src/algorithms/optimization/types.d.ts +5 -0
- package/dist/src/algorithms/optimization/types.js +5 -0
- package/dist/src/algorithms/optimization/types.js.map +1 -0
- package/dist/src/algorithms/planarity/check.d.ts +17 -0
- package/dist/src/algorithms/planarity/check.js +30 -0
- package/dist/src/algorithms/planarity/check.js.map +1 -0
- package/dist/src/algorithms/planarity/embedding.d.ts +29 -0
- package/dist/src/algorithms/planarity/embedding.js +179 -0
- package/dist/src/algorithms/planarity/embedding.js.map +1 -0
- package/dist/src/algorithms/planarity/index.d.ts +7 -0
- package/dist/src/algorithms/planarity/index.js +8 -0
- package/dist/src/algorithms/planarity/index.js.map +1 -0
- package/dist/src/algorithms/planarity/lr-test.d.ts +15 -0
- package/dist/src/algorithms/planarity/lr-test.js +54 -0
- package/dist/src/algorithms/planarity/lr-test.js.map +1 -0
- package/dist/src/algorithms/planarity/special-graphs.d.ts +28 -0
- package/dist/src/algorithms/planarity/special-graphs.js +108 -0
- package/dist/src/algorithms/planarity/special-graphs.js.map +1 -0
- package/dist/src/generators/basic.d.ts +28 -0
- package/dist/src/generators/basic.js +79 -0
- package/dist/src/generators/basic.js.map +1 -0
- package/dist/src/generators/bipartite.d.ts +16 -0
- package/dist/src/generators/bipartite.js +40 -0
- package/dist/src/generators/bipartite.js.map +1 -0
- package/dist/src/generators/grid.d.ts +11 -0
- package/dist/src/generators/grid.js +37 -0
- package/dist/src/generators/grid.js.map +1 -0
- package/dist/src/generators/index.d.ts +8 -0
- package/dist/src/generators/index.js +9 -0
- package/dist/src/generators/index.js.map +1 -0
- package/dist/src/generators/random.d.ts +12 -0
- package/dist/src/generators/random.js +34 -0
- package/dist/src/generators/random.js.map +1 -0
- package/dist/src/generators/scale-free.d.ts +12 -0
- package/dist/src/generators/scale-free.js +62 -0
- package/dist/src/generators/scale-free.js.map +1 -0
- package/dist/src/index.d.ts +10 -0
- package/dist/src/index.js +15 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/layouts/basic/index.d.ts +4 -0
- package/dist/src/layouts/basic/index.js +5 -0
- package/dist/src/layouts/basic/index.js.map +1 -0
- package/dist/src/layouts/basic/random.d.ts +14 -0
- package/dist/src/layouts/basic/random.js +27 -0
- package/dist/src/layouts/basic/random.js.map +1 -0
- package/dist/src/layouts/force-directed/arf.d.ts +13 -0
- package/dist/src/layouts/force-directed/arf.js +103 -0
- package/dist/src/layouts/force-directed/arf.js.map +1 -0
- package/dist/src/layouts/force-directed/forceatlas2.d.ts +22 -0
- package/dist/src/layouts/force-directed/forceatlas2.js +322 -0
- package/dist/src/layouts/force-directed/forceatlas2.js.map +1 -0
- package/dist/src/layouts/force-directed/fruchterman-reingold.d.ts +19 -0
- package/dist/src/layouts/force-directed/fruchterman-reingold.js +131 -0
- package/dist/src/layouts/force-directed/fruchterman-reingold.js.map +1 -0
- package/dist/src/layouts/force-directed/index.d.ts +8 -0
- package/dist/src/layouts/force-directed/index.js +9 -0
- package/dist/src/layouts/force-directed/index.js.map +1 -0
- package/dist/src/layouts/force-directed/kamada-kawai.d.ts +15 -0
- package/dist/src/layouts/force-directed/kamada-kawai.js +87 -0
- package/dist/src/layouts/force-directed/kamada-kawai.js.map +1 -0
- package/dist/src/layouts/force-directed/spring.d.ts +19 -0
- package/dist/src/layouts/force-directed/spring.js +23 -0
- package/dist/src/layouts/force-directed/spring.js.map +1 -0
- package/dist/src/layouts/geometric/circular.d.ts +14 -0
- package/dist/src/layouts/geometric/circular.js +67 -0
- package/dist/src/layouts/geometric/circular.js.map +1 -0
- package/dist/src/layouts/geometric/index.d.ts +6 -0
- package/dist/src/layouts/geometric/index.js +7 -0
- package/dist/src/layouts/geometric/index.js.map +1 -0
- package/dist/src/layouts/geometric/shell.d.ts +15 -0
- package/dist/src/layouts/geometric/shell.js +67 -0
- package/dist/src/layouts/geometric/shell.js.map +1 -0
- package/dist/src/layouts/geometric/spiral.d.ts +16 -0
- package/dist/src/layouts/geometric/spiral.js +73 -0
- package/dist/src/layouts/geometric/spiral.js.map +1 -0
- package/dist/src/layouts/hierarchical/bfs.d.ts +12 -0
- package/dist/src/layouts/hierarchical/bfs.js +62 -0
- package/dist/src/layouts/hierarchical/bfs.js.map +1 -0
- package/dist/src/layouts/hierarchical/bipartite.d.ts +16 -0
- package/dist/src/layouts/hierarchical/bipartite.js +72 -0
- package/dist/src/layouts/hierarchical/bipartite.js.map +1 -0
- package/dist/src/layouts/hierarchical/index.d.ts +6 -0
- package/dist/src/layouts/hierarchical/index.js +7 -0
- package/dist/src/layouts/hierarchical/index.js.map +1 -0
- package/dist/src/layouts/hierarchical/multipartite.d.ts +12 -0
- package/dist/src/layouts/hierarchical/multipartite.js +73 -0
- package/dist/src/layouts/hierarchical/multipartite.js.map +1 -0
- package/dist/src/layouts/index.d.ts +8 -0
- package/dist/src/layouts/index.js +9 -0
- package/dist/src/layouts/index.js.map +1 -0
- package/dist/src/layouts/specialized/index.d.ts +5 -0
- package/dist/src/layouts/specialized/index.js +6 -0
- package/dist/src/layouts/specialized/index.js.map +1 -0
- package/dist/src/layouts/specialized/planar.d.ts +14 -0
- package/dist/src/layouts/specialized/planar.js +47 -0
- package/dist/src/layouts/specialized/planar.js.map +1 -0
- package/dist/src/layouts/specialized/spectral.d.ts +14 -0
- package/dist/src/layouts/specialized/spectral.js +106 -0
- package/dist/src/layouts/specialized/spectral.js.map +1 -0
- package/dist/src/types/embedding.d.ts +9 -0
- package/dist/src/types/embedding.js +5 -0
- package/dist/src/types/embedding.js.map +1 -0
- package/dist/src/types/graph.d.ts +10 -0
- package/dist/src/types/graph.js +5 -0
- package/dist/src/types/graph.js.map +1 -0
- package/dist/src/types/index.d.ts +6 -0
- package/dist/src/types/index.js +7 -0
- package/dist/src/types/index.js.map +1 -0
- package/dist/src/types/layout.d.ts +6 -0
- package/dist/src/types/layout.js +5 -0
- package/dist/src/types/layout.js.map +1 -0
- package/dist/src/utils/graph.d.ts +34 -0
- package/dist/src/utils/graph.js +69 -0
- package/dist/src/utils/graph.js.map +1 -0
- package/dist/src/utils/index.d.ts +8 -0
- package/dist/src/utils/index.js +9 -0
- package/dist/src/utils/index.js.map +1 -0
- package/dist/src/utils/numpy.d.ts +17 -0
- package/dist/src/utils/numpy.js +101 -0
- package/dist/src/utils/numpy.js.map +1 -0
- package/dist/src/utils/params.d.ts +17 -0
- package/dist/src/utils/params.js +22 -0
- package/dist/src/utils/params.js.map +1 -0
- package/dist/src/utils/random.d.ts +14 -0
- package/dist/src/utils/random.js +42 -0
- package/dist/src/utils/random.js.map +1 -0
- package/dist/src/utils/rescale.d.ts +21 -0
- package/dist/src/utils/rescale.js +119 -0
- package/dist/src/utils/rescale.js.map +1 -0
- package/dist/vitest.config.js +1 -1
- package/dist/vitest.config.js.map +1 -1
- package/package.json +14 -1
- package/.env.example +0 -11
- package/.github/workflows/ci.yml +0 -101
- package/.releaserc.json +0 -22
- package/CHANGELOG.md +0 -52
- package/CLAUDE.md +0 -104
- package/CONTRIBUTING.md +0 -1
- package/DEPLOYMENT.md +0 -59
- package/build-examples-script.js +0 -26
- package/commitlint.config.js +0 -37
- package/examples/3d-force-directed.html +0 -611
- package/examples/3d-kamada-kawai.html +0 -379
- package/examples/3d-layout-comparison.html +0 -448
- package/examples/3d-spherical-layout.html +0 -319
- package/examples/arf-layout.html +0 -524
- package/examples/bfs-layout.html +0 -487
- package/examples/bipartite-layout.html +0 -382
- package/examples/circular-layout.html +0 -292
- package/examples/forceatlas2-layout.html +0 -588
- package/examples/index.html +0 -241
- package/examples/kamada-kawai-layout.html +0 -425
- package/examples/layout-helpers.js +0 -23
- package/examples/layout.js +0 -2304
- package/examples/multipartite-layout.html +0 -410
- package/examples/planar-layout.html +0 -388
- package/examples/random-layout.html +0 -328
- package/examples/shell-layout.html +0 -323
- package/examples/spectral-layout.html +0 -335
- package/examples/spiral-layout.html +0 -447
- package/examples/spring-layout.html +0 -313
- package/test/arf-layout.test.ts +0 -443
- package/test/bfs-layout.test.ts +0 -427
- package/test/bipartite-layout.test.ts +0 -344
- package/test/circular-layout.test.ts +0 -442
- package/test/forceatlas2-layout.test.ts +0 -405
- package/test/fruchterman-reingold-layout.test.ts +0 -477
- package/test/graph-generators.test.ts +0 -450
- package/test/kamada-kawai-layout.test.ts +0 -623
- package/test/multipartite-layout.test.ts +0 -404
- package/test/planar-layout.test.ts +0 -266
- package/test/random-layout.test.ts +0 -254
- package/test/rescale-layout.test.ts +0 -373
- package/test/shell-layout.test.ts +0 -347
- package/test/spectral-layout.test.ts +0 -378
- package/test/spiral-layout.test.ts +0 -338
- package/test/spring-layout.test.ts +0 -241
- package/test/test-utils.ts +0 -34
- package/test/utils-graph.test.ts +0 -155
- package/test/utils-index.test.ts +0 -77
- package/test/utils-numpy.test.ts +0 -272
- package/test/utils-params.test.ts +0 -99
- package/test/utils-random.test.ts +0 -229
- package/tsconfig.json +0 -16
- package/vite.config.js +0 -36
- package/vitest.config.ts +0 -30
|
@@ -1,450 +0,0 @@
|
|
|
1
|
-
import { describe, it, assert } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
completeGraph,
|
|
4
|
-
cycleGraph,
|
|
5
|
-
starGraph,
|
|
6
|
-
wheelGraph,
|
|
7
|
-
gridGraph,
|
|
8
|
-
randomGraph,
|
|
9
|
-
bipartiteGraph,
|
|
10
|
-
scaleFreeGraph,
|
|
11
|
-
circularLayout
|
|
12
|
-
} from '../src';
|
|
13
|
-
|
|
14
|
-
describe('Graph Generators', () => {
|
|
15
|
-
describe('completeGraph', () => {
|
|
16
|
-
it('should create complete graph with correct number of nodes and edges', () => {
|
|
17
|
-
const graph = completeGraph(5);
|
|
18
|
-
|
|
19
|
-
assert.equal(graph.nodes().length, 5);
|
|
20
|
-
// Complete graph has n*(n-1)/2 edges
|
|
21
|
-
assert.equal(graph.edges().length, 10);
|
|
22
|
-
|
|
23
|
-
// Check nodes are numbered 0 to n-1
|
|
24
|
-
assert.deepEqual(graph.nodes(), [0, 1, 2, 3, 4]);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('should connect every pair of nodes', () => {
|
|
28
|
-
const graph = completeGraph(4);
|
|
29
|
-
const edges = graph.edges();
|
|
30
|
-
|
|
31
|
-
// Should have edges: [0,1], [0,2], [0,3], [1,2], [1,3], [2,3]
|
|
32
|
-
assert.equal(edges.length, 6);
|
|
33
|
-
|
|
34
|
-
// Check all pairs are connected
|
|
35
|
-
const edgeSet = new Set(edges.map(e => `${Math.min(e[0], e[1])},${Math.max(e[0], e[1])}`));
|
|
36
|
-
assert.isTrue(edgeSet.has('0,1'));
|
|
37
|
-
assert.isTrue(edgeSet.has('0,2'));
|
|
38
|
-
assert.isTrue(edgeSet.has('0,3'));
|
|
39
|
-
assert.isTrue(edgeSet.has('1,2'));
|
|
40
|
-
assert.isTrue(edgeSet.has('1,3'));
|
|
41
|
-
assert.isTrue(edgeSet.has('2,3'));
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('should handle edge cases', () => {
|
|
45
|
-
// Single node
|
|
46
|
-
const g1 = completeGraph(1);
|
|
47
|
-
assert.equal(g1.nodes().length, 1);
|
|
48
|
-
assert.equal(g1.edges().length, 0);
|
|
49
|
-
|
|
50
|
-
// Two nodes
|
|
51
|
-
const g2 = completeGraph(2);
|
|
52
|
-
assert.equal(g2.nodes().length, 2);
|
|
53
|
-
assert.equal(g2.edges().length, 1);
|
|
54
|
-
|
|
55
|
-
// Large graph
|
|
56
|
-
const g10 = completeGraph(10);
|
|
57
|
-
assert.equal(g10.nodes().length, 10);
|
|
58
|
-
assert.equal(g10.edges().length, 45);
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
describe('cycleGraph', () => {
|
|
63
|
-
it('should create cycle with correct number of nodes and edges', () => {
|
|
64
|
-
const graph = cycleGraph(6);
|
|
65
|
-
|
|
66
|
-
assert.equal(graph.nodes().length, 6);
|
|
67
|
-
assert.equal(graph.edges().length, 6); // Cycle has n edges
|
|
68
|
-
|
|
69
|
-
assert.deepEqual(graph.nodes(), [0, 1, 2, 3, 4, 5]);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('should connect nodes in a cycle', () => {
|
|
73
|
-
const graph = cycleGraph(5);
|
|
74
|
-
const edges = graph.edges();
|
|
75
|
-
|
|
76
|
-
// Check cycle connectivity
|
|
77
|
-
const edgeSet = new Set(edges.map(e => `${Math.min(e[0], e[1])},${Math.max(e[0], e[1])}`));
|
|
78
|
-
assert.isTrue(edgeSet.has('0,1'));
|
|
79
|
-
assert.isTrue(edgeSet.has('1,2'));
|
|
80
|
-
assert.isTrue(edgeSet.has('2,3'));
|
|
81
|
-
assert.isTrue(edgeSet.has('3,4'));
|
|
82
|
-
assert.isTrue(edgeSet.has('0,4')); // Closing the cycle
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('should handle small cycles', () => {
|
|
86
|
-
// Triangle
|
|
87
|
-
const g3 = cycleGraph(3);
|
|
88
|
-
assert.equal(g3.nodes().length, 3);
|
|
89
|
-
assert.equal(g3.edges().length, 3);
|
|
90
|
-
|
|
91
|
-
// Square
|
|
92
|
-
const g4 = cycleGraph(4);
|
|
93
|
-
assert.equal(g4.nodes().length, 4);
|
|
94
|
-
assert.equal(g4.edges().length, 4);
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
describe('starGraph', () => {
|
|
99
|
-
it('should create star with correct structure', () => {
|
|
100
|
-
const graph = starGraph(5);
|
|
101
|
-
|
|
102
|
-
assert.equal(graph.nodes().length, 5);
|
|
103
|
-
assert.equal(graph.edges().length, 4); // Star has n-1 edges
|
|
104
|
-
|
|
105
|
-
// Center is node 0
|
|
106
|
-
const edges = graph.edges();
|
|
107
|
-
edges.forEach(edge => {
|
|
108
|
-
assert.isTrue(edge[0] === 0 || edge[1] === 0);
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('should connect all nodes to center', () => {
|
|
113
|
-
const graph = starGraph(6);
|
|
114
|
-
const edges = graph.edges();
|
|
115
|
-
|
|
116
|
-
// Check all leaves connected to center
|
|
117
|
-
const connectedToCenter = new Set();
|
|
118
|
-
edges.forEach(edge => {
|
|
119
|
-
if (edge[0] === 0) connectedToCenter.add(edge[1]);
|
|
120
|
-
if (edge[1] === 0) connectedToCenter.add(edge[0]);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
assert.equal(connectedToCenter.size, 5);
|
|
124
|
-
assert.deepEqual([...connectedToCenter].sort(), [1, 2, 3, 4, 5]);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it('should handle edge cases', () => {
|
|
128
|
-
// Single node
|
|
129
|
-
const g1 = starGraph(1);
|
|
130
|
-
assert.equal(g1.nodes().length, 1);
|
|
131
|
-
assert.equal(g1.edges().length, 0);
|
|
132
|
-
|
|
133
|
-
// Two nodes
|
|
134
|
-
const g2 = starGraph(2);
|
|
135
|
-
assert.equal(g2.nodes().length, 2);
|
|
136
|
-
assert.equal(g2.edges().length, 1);
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
describe('wheelGraph', () => {
|
|
141
|
-
it('should create wheel with correct structure', () => {
|
|
142
|
-
const graph = wheelGraph(6);
|
|
143
|
-
|
|
144
|
-
assert.equal(graph.nodes().length, 6);
|
|
145
|
-
// Wheel has 2*(n-1) edges: n-1 spokes + n-1 rim edges
|
|
146
|
-
assert.equal(graph.edges().length, 10);
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
it('should have hub connected to all rim nodes', () => {
|
|
150
|
-
const graph = wheelGraph(5);
|
|
151
|
-
const edges = graph.edges();
|
|
152
|
-
|
|
153
|
-
// Node 0 is the hub
|
|
154
|
-
const hubEdges = edges.filter(e => e[0] === 0 || e[1] === 0);
|
|
155
|
-
assert.equal(hubEdges.length, 4); // Connected to all 4 rim nodes
|
|
156
|
-
|
|
157
|
-
// Check rim forms a cycle
|
|
158
|
-
const rimEdges = edges.filter(e => e[0] !== 0 && e[1] !== 0);
|
|
159
|
-
assert.equal(rimEdges.length, 4); // 4 edges in the rim cycle
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it('should handle small wheels', () => {
|
|
163
|
-
// Smallest wheel (triangle with center)
|
|
164
|
-
const g4 = wheelGraph(4);
|
|
165
|
-
assert.equal(g4.nodes().length, 4);
|
|
166
|
-
assert.equal(g4.edges().length, 6);
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
describe('gridGraph', () => {
|
|
171
|
-
it('should create grid with correct dimensions', () => {
|
|
172
|
-
const graph = gridGraph(3, 4);
|
|
173
|
-
|
|
174
|
-
assert.equal(graph.nodes().length, 12); // 3*4 nodes
|
|
175
|
-
// Grid edges: (m-1)*n + m*(n-1)
|
|
176
|
-
assert.equal(graph.edges().length, 17);
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
it('should use coordinate naming for nodes', () => {
|
|
180
|
-
const graph = gridGraph(2, 3);
|
|
181
|
-
const nodes = graph.nodes();
|
|
182
|
-
|
|
183
|
-
assert.deepEqual(nodes.sort(), ['0,0', '0,1', '0,2', '1,0', '1,1', '1,2']);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
it('should connect grid neighbors correctly', () => {
|
|
187
|
-
const graph = gridGraph(3, 3);
|
|
188
|
-
const edges = graph.edges();
|
|
189
|
-
|
|
190
|
-
// Check some specific connections
|
|
191
|
-
const edgeStrings = edges.map(e => `${e[0]}-${e[1]}`);
|
|
192
|
-
|
|
193
|
-
// Horizontal connections
|
|
194
|
-
assert.isTrue(edgeStrings.includes('0,0-0,1') || edgeStrings.includes('0,1-0,0'));
|
|
195
|
-
assert.isTrue(edgeStrings.includes('0,1-0,2') || edgeStrings.includes('0,2-0,1'));
|
|
196
|
-
|
|
197
|
-
// Vertical connections
|
|
198
|
-
assert.isTrue(edgeStrings.includes('0,0-1,0') || edgeStrings.includes('1,0-0,0'));
|
|
199
|
-
assert.isTrue(edgeStrings.includes('1,0-2,0') || edgeStrings.includes('2,0-1,0'));
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it('should handle edge cases', () => {
|
|
203
|
-
// 1x1 grid
|
|
204
|
-
const g1 = gridGraph(1, 1);
|
|
205
|
-
assert.equal(g1.nodes().length, 1);
|
|
206
|
-
assert.equal(g1.edges().length, 0);
|
|
207
|
-
|
|
208
|
-
// Line graphs
|
|
209
|
-
const line = gridGraph(1, 5);
|
|
210
|
-
assert.equal(line.nodes().length, 5);
|
|
211
|
-
assert.equal(line.edges().length, 4);
|
|
212
|
-
|
|
213
|
-
const vline = gridGraph(5, 1);
|
|
214
|
-
assert.equal(vline.nodes().length, 5);
|
|
215
|
-
assert.equal(vline.edges().length, 4);
|
|
216
|
-
});
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
describe('randomGraph', () => {
|
|
220
|
-
it('should create graph with specified number of nodes', () => {
|
|
221
|
-
const graph = randomGraph(10, 0.3, 123);
|
|
222
|
-
|
|
223
|
-
assert.equal(graph.nodes().length, 10);
|
|
224
|
-
assert.deepEqual(graph.nodes(), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
it('should respect edge probability', () => {
|
|
228
|
-
// With p=0, no edges
|
|
229
|
-
const g0 = randomGraph(10, 0, 123);
|
|
230
|
-
assert.equal(g0.edges().length, 0);
|
|
231
|
-
|
|
232
|
-
// With p=1, complete graph
|
|
233
|
-
const g1 = randomGraph(10, 1, 123);
|
|
234
|
-
assert.equal(g1.edges().length, 45); // 10*9/2
|
|
235
|
-
|
|
236
|
-
// With p=0.5, roughly half of possible edges
|
|
237
|
-
const g05 = randomGraph(20, 0.5, 123);
|
|
238
|
-
const edgeCount = g05.edges().length;
|
|
239
|
-
const maxEdges = 20 * 19 / 2;
|
|
240
|
-
assert.isAbove(edgeCount, maxEdges * 0.3);
|
|
241
|
-
assert.isBelow(edgeCount, maxEdges * 0.7);
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
it('should produce consistent results with same seed', () => {
|
|
245
|
-
const g1 = randomGraph(15, 0.3, 42);
|
|
246
|
-
const g2 = randomGraph(15, 0.3, 42);
|
|
247
|
-
|
|
248
|
-
assert.equal(g1.edges().length, g2.edges().length);
|
|
249
|
-
|
|
250
|
-
const edges1 = g1.edges().map(e => `${e[0]},${e[1]}`).sort();
|
|
251
|
-
const edges2 = g2.edges().map(e => `${e[0]},${e[1]}`).sort();
|
|
252
|
-
assert.deepEqual(edges1, edges2);
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
it('should produce different results with different seeds', () => {
|
|
256
|
-
const g1 = randomGraph(15, 0.5, 123);
|
|
257
|
-
const g2 = randomGraph(15, 0.5, 456);
|
|
258
|
-
|
|
259
|
-
// Very unlikely to have same edge count
|
|
260
|
-
assert.notEqual(g1.edges().length, g2.edges().length);
|
|
261
|
-
});
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
describe('bipartiteGraph', () => {
|
|
265
|
-
it('should create bipartite graph with two sets', () => {
|
|
266
|
-
const graph = bipartiteGraph(3, 4, 0.5, 123);
|
|
267
|
-
|
|
268
|
-
assert.equal(graph.nodes().length, 7); // 3 + 4
|
|
269
|
-
assert.deepEqual(graph.setA, ['A0', 'A1', 'A2']);
|
|
270
|
-
assert.deepEqual(graph.setB, ['B0', 'B1', 'B2', 'B3']);
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
it('should only connect nodes between sets', () => {
|
|
274
|
-
const graph = bipartiteGraph(3, 3, 1.0, 123); // Complete bipartite
|
|
275
|
-
const edges = graph.edges();
|
|
276
|
-
|
|
277
|
-
assert.equal(edges.length, 9); // 3*3
|
|
278
|
-
|
|
279
|
-
// Check all edges go between sets
|
|
280
|
-
edges.forEach(edge => {
|
|
281
|
-
const hasA = edge.some(node => (node as string).startsWith('A'));
|
|
282
|
-
const hasB = edge.some(node => (node as string).startsWith('B'));
|
|
283
|
-
assert.isTrue(hasA && hasB);
|
|
284
|
-
});
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
it('should respect edge probability', () => {
|
|
288
|
-
// No edges
|
|
289
|
-
const g0 = bipartiteGraph(5, 5, 0, 123);
|
|
290
|
-
assert.equal(g0.edges().length, 0);
|
|
291
|
-
|
|
292
|
-
// All possible edges
|
|
293
|
-
const g1 = bipartiteGraph(4, 6, 1, 123);
|
|
294
|
-
assert.equal(g1.edges().length, 24); // 4*6
|
|
295
|
-
|
|
296
|
-
// Partial edges
|
|
297
|
-
const g05 = bipartiteGraph(10, 10, 0.5, 123);
|
|
298
|
-
const edgeCount = g05.edges().length;
|
|
299
|
-
assert.isAbove(edgeCount, 30); // Should have some edges
|
|
300
|
-
assert.isBelow(edgeCount, 70); // But not all 100
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
it('should produce consistent results with seed', () => {
|
|
304
|
-
const g1 = bipartiteGraph(5, 7, 0.4, 42);
|
|
305
|
-
const g2 = bipartiteGraph(5, 7, 0.4, 42);
|
|
306
|
-
|
|
307
|
-
const edges1 = g1.edges().map(e => `${e[0]}-${e[1]}`).sort();
|
|
308
|
-
const edges2 = g2.edges().map(e => `${e[0]}-${e[1]}`).sort();
|
|
309
|
-
|
|
310
|
-
assert.deepEqual(edges1, edges2);
|
|
311
|
-
});
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
describe('scaleFreeGraph', () => {
|
|
315
|
-
it('should create scale-free graph with preferential attachment', () => {
|
|
316
|
-
const graph = scaleFreeGraph(20, 2, 123);
|
|
317
|
-
|
|
318
|
-
assert.equal(graph.nodes().length, 20);
|
|
319
|
-
// Each new node adds m edges
|
|
320
|
-
assert.isAtLeast(graph.edges().length, 35); // Initial complete graph + added edges
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
it('should start with complete graph of m+1 nodes', () => {
|
|
324
|
-
const graph = scaleFreeGraph(10, 3, 123);
|
|
325
|
-
const edges = graph.edges();
|
|
326
|
-
|
|
327
|
-
// First 4 nodes (0,1,2,3) should form complete graph
|
|
328
|
-
const initialEdges = edges.filter(e =>
|
|
329
|
-
e[0] <= 3 && e[1] <= 3
|
|
330
|
-
);
|
|
331
|
-
|
|
332
|
-
assert.equal(initialEdges.length, 6); // 4*3/2
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
it('should throw error if m >= n', () => {
|
|
336
|
-
assert.throws(() => {
|
|
337
|
-
scaleFreeGraph(5, 5);
|
|
338
|
-
}, 'm must be less than n');
|
|
339
|
-
|
|
340
|
-
assert.throws(() => {
|
|
341
|
-
scaleFreeGraph(5, 6);
|
|
342
|
-
}, 'm must be less than n');
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
it('should create graphs with power-law degree distribution', () => {
|
|
346
|
-
const graph = scaleFreeGraph(100, 2, 123);
|
|
347
|
-
|
|
348
|
-
// Calculate degree distribution
|
|
349
|
-
const degrees = new Map();
|
|
350
|
-
graph.nodes().forEach(node => {
|
|
351
|
-
degrees.set(node, 0);
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
graph.edges().forEach(edge => {
|
|
355
|
-
degrees.set(edge[0], degrees.get(edge[0]) + 1);
|
|
356
|
-
degrees.set(edge[1], degrees.get(edge[1]) + 1);
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
// Should have some high-degree nodes (hubs)
|
|
360
|
-
const degreeValues = Array.from(degrees.values());
|
|
361
|
-
const maxDegree = Math.max(...degreeValues);
|
|
362
|
-
const avgDegree = degreeValues.reduce((a, b) => a + b) / degreeValues.length;
|
|
363
|
-
|
|
364
|
-
assert.isAbove(maxDegree, avgDegree * 2); // Should have hubs
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
it('should produce consistent results with seed', () => {
|
|
368
|
-
const g1 = scaleFreeGraph(15, 2, 42);
|
|
369
|
-
const g2 = scaleFreeGraph(15, 2, 42);
|
|
370
|
-
|
|
371
|
-
assert.equal(g1.edges().length, g2.edges().length);
|
|
372
|
-
|
|
373
|
-
const edges1 = g1.edges().map(e => `${Math.min(e[0], e[1])},${Math.max(e[0], e[1])}`).sort();
|
|
374
|
-
const edges2 = g2.edges().map(e => `${Math.min(e[0], e[1])},${Math.max(e[0], e[1])}`).sort();
|
|
375
|
-
|
|
376
|
-
assert.deepEqual(edges1, edges2);
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
it('should handle different m values', () => {
|
|
380
|
-
const g1 = scaleFreeGraph(20, 1, 123);
|
|
381
|
-
const g2 = scaleFreeGraph(20, 3, 123);
|
|
382
|
-
const g3 = scaleFreeGraph(20, 5, 123);
|
|
383
|
-
|
|
384
|
-
// More connections per new node means more edges
|
|
385
|
-
assert.isBelow(g1.edges().length, g2.edges().length);
|
|
386
|
-
assert.isBelow(g2.edges().length, g3.edges().length);
|
|
387
|
-
});
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
describe('Graph generator integration', () => {
|
|
391
|
-
it('should work with layout algorithms', () => {
|
|
392
|
-
// Test each generator with a layout
|
|
393
|
-
const generators = [
|
|
394
|
-
() => completeGraph(5),
|
|
395
|
-
() => cycleGraph(6),
|
|
396
|
-
() => starGraph(7),
|
|
397
|
-
() => wheelGraph(5),
|
|
398
|
-
() => gridGraph(3, 3),
|
|
399
|
-
() => randomGraph(10, 0.3, 123),
|
|
400
|
-
() => bipartiteGraph(3, 4, 0.5, 123),
|
|
401
|
-
() => scaleFreeGraph(15, 2, 123)
|
|
402
|
-
];
|
|
403
|
-
|
|
404
|
-
generators.forEach(gen => {
|
|
405
|
-
const graph = gen();
|
|
406
|
-
const positions = circularLayout(graph);
|
|
407
|
-
|
|
408
|
-
// Should position all nodes
|
|
409
|
-
assert.equal(Object.keys(positions).length, graph.nodes().length);
|
|
410
|
-
graph.nodes().forEach(node => {
|
|
411
|
-
assert.isDefined(positions[node]);
|
|
412
|
-
assert.equal(positions[node].length, 2);
|
|
413
|
-
});
|
|
414
|
-
});
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
it('should produce valid graph structure', () => {
|
|
418
|
-
const generators = [
|
|
419
|
-
() => completeGraph(5),
|
|
420
|
-
() => cycleGraph(6),
|
|
421
|
-
() => starGraph(7),
|
|
422
|
-
() => wheelGraph(5),
|
|
423
|
-
() => gridGraph(3, 3),
|
|
424
|
-
() => randomGraph(10, 0.3, 123),
|
|
425
|
-
() => bipartiteGraph(3, 4, 0.5, 123),
|
|
426
|
-
() => scaleFreeGraph(15, 2, 123)
|
|
427
|
-
];
|
|
428
|
-
|
|
429
|
-
generators.forEach(gen => {
|
|
430
|
-
const graph = gen();
|
|
431
|
-
|
|
432
|
-
// Should have nodes() and edges() methods
|
|
433
|
-
assert.isFunction(graph.nodes);
|
|
434
|
-
assert.isFunction(graph.edges);
|
|
435
|
-
|
|
436
|
-
// Should return arrays
|
|
437
|
-
assert.isArray(graph.nodes());
|
|
438
|
-
assert.isArray(graph.edges());
|
|
439
|
-
|
|
440
|
-
// Each edge should connect valid nodes
|
|
441
|
-
const nodeSet = new Set(graph.nodes());
|
|
442
|
-
graph.edges().forEach(edge => {
|
|
443
|
-
assert.equal(edge.length, 2);
|
|
444
|
-
assert.isTrue(nodeSet.has(edge[0]));
|
|
445
|
-
assert.isTrue(nodeSet.has(edge[1]));
|
|
446
|
-
});
|
|
447
|
-
});
|
|
448
|
-
});
|
|
449
|
-
});
|
|
450
|
-
});
|