@graphty/layout 1.2.2 → 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.
Files changed (200) hide show
  1. package/README.md +317 -237
  2. package/dist/src/algorithms/index.d.ts +5 -0
  3. package/dist/src/algorithms/index.js +6 -0
  4. package/dist/src/algorithms/index.js.map +1 -0
  5. package/dist/src/algorithms/optimization/index.d.ts +7 -0
  6. package/dist/src/algorithms/optimization/index.js +7 -0
  7. package/dist/src/algorithms/optimization/index.js.map +1 -0
  8. package/dist/src/algorithms/optimization/kamada-kawai-solver.d.ts +32 -0
  9. package/dist/src/algorithms/optimization/kamada-kawai-solver.js +176 -0
  10. package/dist/src/algorithms/optimization/kamada-kawai-solver.js.map +1 -0
  11. package/dist/src/algorithms/optimization/lbfgs.d.ts +13 -0
  12. package/dist/src/algorithms/optimization/lbfgs.js +56 -0
  13. package/dist/src/algorithms/optimization/lbfgs.js.map +1 -0
  14. package/dist/src/algorithms/optimization/line-search.d.ts +15 -0
  15. package/dist/src/algorithms/optimization/line-search.js +37 -0
  16. package/dist/src/algorithms/optimization/line-search.js.map +1 -0
  17. package/dist/src/algorithms/optimization/types.d.ts +5 -0
  18. package/dist/src/algorithms/optimization/types.js +5 -0
  19. package/dist/src/algorithms/optimization/types.js.map +1 -0
  20. package/dist/src/algorithms/planarity/check.d.ts +17 -0
  21. package/dist/src/algorithms/planarity/check.js +30 -0
  22. package/dist/src/algorithms/planarity/check.js.map +1 -0
  23. package/dist/src/algorithms/planarity/embedding.d.ts +29 -0
  24. package/dist/src/algorithms/planarity/embedding.js +179 -0
  25. package/dist/src/algorithms/planarity/embedding.js.map +1 -0
  26. package/dist/src/algorithms/planarity/index.d.ts +7 -0
  27. package/dist/src/algorithms/planarity/index.js +8 -0
  28. package/dist/src/algorithms/planarity/index.js.map +1 -0
  29. package/dist/src/algorithms/planarity/lr-test.d.ts +15 -0
  30. package/dist/src/algorithms/planarity/lr-test.js +54 -0
  31. package/dist/src/algorithms/planarity/lr-test.js.map +1 -0
  32. package/dist/src/algorithms/planarity/special-graphs.d.ts +28 -0
  33. package/dist/src/algorithms/planarity/special-graphs.js +108 -0
  34. package/dist/src/algorithms/planarity/special-graphs.js.map +1 -0
  35. package/dist/src/generators/basic.d.ts +28 -0
  36. package/dist/src/generators/basic.js +79 -0
  37. package/dist/src/generators/basic.js.map +1 -0
  38. package/dist/src/generators/bipartite.d.ts +16 -0
  39. package/dist/src/generators/bipartite.js +40 -0
  40. package/dist/src/generators/bipartite.js.map +1 -0
  41. package/dist/src/generators/grid.d.ts +11 -0
  42. package/dist/src/generators/grid.js +37 -0
  43. package/dist/src/generators/grid.js.map +1 -0
  44. package/dist/src/generators/index.d.ts +8 -0
  45. package/dist/src/generators/index.js +9 -0
  46. package/dist/src/generators/index.js.map +1 -0
  47. package/dist/src/generators/random.d.ts +12 -0
  48. package/dist/src/generators/random.js +34 -0
  49. package/dist/src/generators/random.js.map +1 -0
  50. package/dist/src/generators/scale-free.d.ts +12 -0
  51. package/dist/src/generators/scale-free.js +62 -0
  52. package/dist/src/generators/scale-free.js.map +1 -0
  53. package/dist/src/index.d.ts +10 -0
  54. package/dist/src/index.js +15 -0
  55. package/dist/src/index.js.map +1 -0
  56. package/dist/src/layouts/basic/index.d.ts +4 -0
  57. package/dist/src/layouts/basic/index.js +5 -0
  58. package/dist/src/layouts/basic/index.js.map +1 -0
  59. package/dist/src/layouts/basic/random.d.ts +14 -0
  60. package/dist/src/layouts/basic/random.js +27 -0
  61. package/dist/src/layouts/basic/random.js.map +1 -0
  62. package/dist/src/layouts/force-directed/arf.d.ts +13 -0
  63. package/dist/src/layouts/force-directed/arf.js +103 -0
  64. package/dist/src/layouts/force-directed/arf.js.map +1 -0
  65. package/dist/src/layouts/force-directed/forceatlas2.d.ts +22 -0
  66. package/dist/src/layouts/force-directed/forceatlas2.js +322 -0
  67. package/dist/src/layouts/force-directed/forceatlas2.js.map +1 -0
  68. package/dist/src/layouts/force-directed/fruchterman-reingold.d.ts +19 -0
  69. package/dist/src/layouts/force-directed/fruchterman-reingold.js +131 -0
  70. package/dist/src/layouts/force-directed/fruchterman-reingold.js.map +1 -0
  71. package/dist/src/layouts/force-directed/index.d.ts +8 -0
  72. package/dist/src/layouts/force-directed/index.js +9 -0
  73. package/dist/src/layouts/force-directed/index.js.map +1 -0
  74. package/dist/src/layouts/force-directed/kamada-kawai.d.ts +15 -0
  75. package/dist/src/layouts/force-directed/kamada-kawai.js +87 -0
  76. package/dist/src/layouts/force-directed/kamada-kawai.js.map +1 -0
  77. package/dist/src/layouts/force-directed/spring.d.ts +19 -0
  78. package/dist/src/layouts/force-directed/spring.js +23 -0
  79. package/dist/src/layouts/force-directed/spring.js.map +1 -0
  80. package/dist/src/layouts/geometric/circular.d.ts +14 -0
  81. package/dist/src/layouts/geometric/circular.js +67 -0
  82. package/dist/src/layouts/geometric/circular.js.map +1 -0
  83. package/dist/src/layouts/geometric/index.d.ts +6 -0
  84. package/dist/src/layouts/geometric/index.js +7 -0
  85. package/dist/src/layouts/geometric/index.js.map +1 -0
  86. package/dist/src/layouts/geometric/shell.d.ts +15 -0
  87. package/dist/src/layouts/geometric/shell.js +67 -0
  88. package/dist/src/layouts/geometric/shell.js.map +1 -0
  89. package/dist/src/layouts/geometric/spiral.d.ts +16 -0
  90. package/dist/src/layouts/geometric/spiral.js +73 -0
  91. package/dist/src/layouts/geometric/spiral.js.map +1 -0
  92. package/dist/src/layouts/hierarchical/bfs.d.ts +12 -0
  93. package/dist/src/layouts/hierarchical/bfs.js +62 -0
  94. package/dist/src/layouts/hierarchical/bfs.js.map +1 -0
  95. package/dist/src/layouts/hierarchical/bipartite.d.ts +16 -0
  96. package/dist/src/layouts/hierarchical/bipartite.js +72 -0
  97. package/dist/src/layouts/hierarchical/bipartite.js.map +1 -0
  98. package/dist/src/layouts/hierarchical/index.d.ts +6 -0
  99. package/dist/src/layouts/hierarchical/index.js +7 -0
  100. package/dist/src/layouts/hierarchical/index.js.map +1 -0
  101. package/dist/src/layouts/hierarchical/multipartite.d.ts +12 -0
  102. package/dist/src/layouts/hierarchical/multipartite.js +73 -0
  103. package/dist/src/layouts/hierarchical/multipartite.js.map +1 -0
  104. package/dist/src/layouts/index.d.ts +8 -0
  105. package/dist/src/layouts/index.js +9 -0
  106. package/dist/src/layouts/index.js.map +1 -0
  107. package/dist/src/layouts/specialized/index.d.ts +5 -0
  108. package/dist/src/layouts/specialized/index.js +6 -0
  109. package/dist/src/layouts/specialized/index.js.map +1 -0
  110. package/dist/src/layouts/specialized/planar.d.ts +14 -0
  111. package/dist/src/layouts/specialized/planar.js +47 -0
  112. package/dist/src/layouts/specialized/planar.js.map +1 -0
  113. package/dist/src/layouts/specialized/spectral.d.ts +14 -0
  114. package/dist/src/layouts/specialized/spectral.js +106 -0
  115. package/dist/src/layouts/specialized/spectral.js.map +1 -0
  116. package/dist/src/types/embedding.d.ts +9 -0
  117. package/dist/src/types/embedding.js +5 -0
  118. package/dist/src/types/embedding.js.map +1 -0
  119. package/dist/src/types/graph.d.ts +10 -0
  120. package/dist/src/types/graph.js +5 -0
  121. package/dist/src/types/graph.js.map +1 -0
  122. package/dist/src/types/index.d.ts +6 -0
  123. package/dist/src/types/index.js +7 -0
  124. package/dist/src/types/index.js.map +1 -0
  125. package/dist/src/types/layout.d.ts +6 -0
  126. package/dist/src/types/layout.js +5 -0
  127. package/dist/src/types/layout.js.map +1 -0
  128. package/dist/src/utils/graph.d.ts +34 -0
  129. package/dist/src/utils/graph.js +69 -0
  130. package/dist/src/utils/graph.js.map +1 -0
  131. package/dist/src/utils/index.d.ts +8 -0
  132. package/dist/src/utils/index.js +9 -0
  133. package/dist/src/utils/index.js.map +1 -0
  134. package/dist/src/utils/numpy.d.ts +17 -0
  135. package/dist/src/utils/numpy.js +101 -0
  136. package/dist/src/utils/numpy.js.map +1 -0
  137. package/dist/src/utils/params.d.ts +17 -0
  138. package/dist/src/utils/params.js +22 -0
  139. package/dist/src/utils/params.js.map +1 -0
  140. package/dist/src/utils/random.d.ts +14 -0
  141. package/dist/src/utils/random.js +42 -0
  142. package/dist/src/utils/random.js.map +1 -0
  143. package/dist/src/utils/rescale.d.ts +21 -0
  144. package/dist/src/utils/rescale.js +119 -0
  145. package/dist/src/utils/rescale.js.map +1 -0
  146. package/dist/vitest.config.js +1 -1
  147. package/dist/vitest.config.js.map +1 -1
  148. package/package.json +15 -1
  149. package/.env.example +0 -11
  150. package/.github/workflows/ci.yml +0 -101
  151. package/.releaserc.json +0 -22
  152. package/CHANGELOG.md +0 -45
  153. package/CLAUDE.md +0 -104
  154. package/CONTRIBUTING.md +0 -1
  155. package/DEPLOYMENT.md +0 -59
  156. package/commitlint.config.js +0 -37
  157. package/examples/3d-force-directed.html +0 -611
  158. package/examples/3d-kamada-kawai.html +0 -379
  159. package/examples/3d-layout-comparison.html +0 -448
  160. package/examples/3d-spherical-layout.html +0 -319
  161. package/examples/arf-layout.html +0 -524
  162. package/examples/bfs-layout.html +0 -487
  163. package/examples/bipartite-layout.html +0 -382
  164. package/examples/circular-layout.html +0 -292
  165. package/examples/forceatlas2-layout.html +0 -588
  166. package/examples/index.html +0 -241
  167. package/examples/kamada-kawai-layout.html +0 -425
  168. package/examples/layout-helpers.js +0 -486
  169. package/examples/multipartite-layout.html +0 -410
  170. package/examples/planar-layout.html +0 -388
  171. package/examples/random-layout.html +0 -328
  172. package/examples/shell-layout.html +0 -323
  173. package/examples/spectral-layout.html +0 -335
  174. package/examples/spiral-layout.html +0 -447
  175. package/examples/spring-layout.html +0 -313
  176. package/test/arf-layout.test.ts +0 -443
  177. package/test/bfs-layout.test.ts +0 -427
  178. package/test/bipartite-layout.test.ts +0 -344
  179. package/test/circular-layout.test.ts +0 -442
  180. package/test/forceatlas2-layout.test.ts +0 -405
  181. package/test/fruchterman-reingold-layout.test.ts +0 -477
  182. package/test/graph-generators.test.ts +0 -450
  183. package/test/kamada-kawai-layout.test.ts +0 -623
  184. package/test/multipartite-layout.test.ts +0 -404
  185. package/test/planar-layout.test.ts +0 -266
  186. package/test/random-layout.test.ts +0 -254
  187. package/test/rescale-layout.test.ts +0 -373
  188. package/test/shell-layout.test.ts +0 -347
  189. package/test/spectral-layout.test.ts +0 -378
  190. package/test/spiral-layout.test.ts +0 -338
  191. package/test/spring-layout.test.ts +0 -241
  192. package/test/test-utils.ts +0 -34
  193. package/test/utils-graph.test.ts +0 -155
  194. package/test/utils-index.test.ts +0 -77
  195. package/test/utils-numpy.test.ts +0 -272
  196. package/test/utils-params.test.ts +0 -99
  197. package/test/utils-random.test.ts +0 -229
  198. package/tsconfig.json +0 -16
  199. package/vite.config.js +0 -36
  200. package/vitest.config.ts +0 -30
@@ -1,611 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <!-- Eruda Mobile Console -->
5
- <script src="https://cdn.jsdelivr.net/npm/eruda@3/eruda.min.js"></script>
6
- <script>
7
- // Initialize Eruda console for mobile debugging
8
- if (typeof eruda !== "undefined") {
9
- eruda.init();
10
- // Auto-show on mobile devices
11
- if (/mobile|android|ios|iphone|ipad|ipod/i.test(navigator.userAgent.toLowerCase())) {
12
- eruda.show();
13
- }
14
- }
15
- </script>
16
- <meta charset="UTF-8">
17
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
18
- <title>3D Force-Directed Layouts - @graphty/layout</title>
19
- <style>
20
- body {
21
- margin: 0;
22
- font-family: Arial, sans-serif;
23
- overflow: hidden;
24
- }
25
- #container {
26
- width: 100vw;
27
- height: 100vh;
28
- }
29
- #info {
30
- position: absolute;
31
- top: 10px;
32
- left: 10px;
33
- color: white;
34
- background: rgba(0, 0, 0, 0.7);
35
- padding: 10px;
36
- border-radius: 5px;
37
- }
38
- #controls {
39
- position: absolute;
40
- top: 10px;
41
- right: 10px;
42
- background: rgba(0, 0, 0, 0.7);
43
- padding: 10px;
44
- border-radius: 5px;
45
- color: white;
46
- max-width: 250px;
47
- }
48
- button {
49
- display: block;
50
- margin: 5px 0;
51
- padding: 5px 10px;
52
- cursor: pointer;
53
- width: 100%;
54
- }
55
- label {
56
- display: block;
57
- margin: 8px 0;
58
- }
59
- select, input[type="range"] {
60
- width: 100%;
61
- }
62
- .algorithm-params {
63
- margin-top: 10px;
64
- padding-top: 10px;
65
- border-top: 1px solid #555;
66
- }
67
- </style>
68
- </head>
69
- <body>
70
- <div id="container"></div>
71
- <div id="info">
72
- <h2>3D Force-Directed Layouts</h2>
73
- <p>Compare different force-directed algorithms in 3D</p>
74
- <p>Drag to rotate • Scroll to zoom</p>
75
- </div>
76
- <div id="controls">
77
- <label>
78
- Algorithm:
79
- <select id="algorithm">
80
- <option value="spring">Spring Layout</option>
81
- <option value="forceatlas2">ForceAtlas2</option>
82
- <option value="arf">ARF Layout</option>
83
- </select>
84
- </label>
85
- <label>
86
- Graph Type:
87
- <select id="graphType">
88
- <option value="random">Random Graph</option>
89
- <option value="scalefree">Scale-Free Graph</option>
90
- <option value="complete">Complete Graph</option>
91
- <option value="bipartite">Bipartite Graph</option>
92
- <option value="grid">Grid Graph</option>
93
- </select>
94
- </label>
95
- <label>
96
- Node Count: <span id="nodeCountLabel">25</span>
97
- <input type="range" id="nodeCount" min="10" max="50" value="25">
98
- </label>
99
- <button id="regenerate">Generate New Graph</button>
100
-
101
- <div class="algorithm-params">
102
- <h4>Algorithm Parameters</h4>
103
- <label>
104
- Iterations: <span id="iterationsLabel">50</span>
105
- <input type="range" id="iterations" min="10" max="200" value="50">
106
- </label>
107
- <label id="springConstantControl">
108
- Spring Constant: <span id="springLabel">1</span>
109
- <input type="range" id="springConstant" min="0.1" max="5" step="0.1" value="1">
110
- </label>
111
- <label id="gravityControl">
112
- Gravity: <span id="gravityLabel">1</span>
113
- <input type="range" id="gravity" min="0" max="10" step="0.5" value="1">
114
- </label>
115
- <label id="attractionControl">
116
- Attraction: <span id="attractionLabel">0.1</span>
117
- <input type="range" id="attraction" min="0" max="1" step="0.05" value="0.1">
118
- </label>
119
- </div>
120
-
121
- <label>
122
- <input type="checkbox" id="showParticles"> Show Force Particles
123
- </label>
124
- <label>
125
- <input type="checkbox" id="animateLayout" checked> Animate Transition
126
- </label>
127
- </div>
128
-
129
- <!-- Import map for module resolution -->
130
- <script type="importmap">
131
- {
132
- "imports": {
133
- "three": "https://unpkg.com/three@0.157.0/build/three.module.js",
134
- "three/addons/": "https://unpkg.com/three@0.157.0/examples/jsm/"
135
- }
136
- }
137
- </script>
138
-
139
- <script type="module">
140
- console.log('Script starting...');
141
-
142
- import * as THREE from 'https://unpkg.com/three@0.157.0/build/three.module.js';
143
- import { OrbitControls } from 'https://unpkg.com/three@0.157.0/examples/jsm/controls/OrbitControls.js';
144
- import {
145
- springLayout,
146
- forceatlas2Layout,
147
- arfLayout,
148
- randomGraph,
149
- scaleFreeGraph,
150
- completeGraph,
151
- bipartiteGraph,
152
- gridGraph
153
- } from './layout.js';
154
-
155
- console.log('All modules imported successfully');
156
- console.log('Layout functions available:', {
157
- springLayout: typeof springLayout,
158
- randomGraph: typeof randomGraph
159
- });
160
-
161
- // Test graph generation
162
- console.log('Testing graph generation...');
163
- const testGraph = randomGraph(5, 0.5, 42);
164
- console.log('Test graph created:', {
165
- nodes: testGraph.nodes(),
166
- edges: testGraph.edges(),
167
- nodeCount: testGraph.nodes().length,
168
- edgeCount: testGraph.edges().length
169
- });
170
-
171
- // Test layout function
172
- console.log('Testing spring layout...');
173
- const testPositions = springLayout(testGraph, 1, null, null, 10, 1, [0, 0, 0], 3);
174
- console.log('Test layout positions:', testPositions);
175
- console.log('Number of positioned nodes:', Object.keys(testPositions).length);
176
-
177
- try {
178
-
179
- // Three.js setup
180
- console.log('Setting up Three.js scene...');
181
- const scene = new THREE.Scene();
182
- scene.background = new THREE.Color(0x0a0a0a);
183
- scene.fog = new THREE.Fog(0x0a0a0a, 500, 1500);
184
-
185
- const camera = new THREE.PerspectiveCamera(
186
- 75,
187
- window.innerWidth / window.innerHeight,
188
- 0.1,
189
- 2000
190
- );
191
- camera.position.set(400, 300, 500);
192
-
193
- const renderer = new THREE.WebGLRenderer({ antialias: true });
194
- renderer.setSize(window.innerWidth, window.innerHeight);
195
- document.getElementById('container').appendChild(renderer.domElement);
196
-
197
- const controls = new OrbitControls(camera, renderer.domElement);
198
- controls.enableDamping = true;
199
- controls.dampingFactor = 0.05;
200
-
201
- // Lighting
202
- const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);
203
- scene.add(ambientLight);
204
-
205
- const pointLight1 = new THREE.PointLight(0xff0044, 1, 500);
206
- pointLight1.position.set(200, 200, 200);
207
- scene.add(pointLight1);
208
-
209
- const pointLight2 = new THREE.PointLight(0x0044ff, 1, 500);
210
- pointLight2.position.set(-200, -200, -200);
211
- scene.add(pointLight2);
212
-
213
- // Graph visualization
214
- let nodeGroup = new THREE.Group();
215
- let edgeGroup = new THREE.Group();
216
- let particleGroup = new THREE.Group();
217
- scene.add(nodeGroup);
218
- scene.add(edgeGroup);
219
- scene.add(particleGroup);
220
-
221
- console.log('Three.js setup complete');
222
-
223
- // Animation state
224
- let currentPositions = {};
225
- let targetPositions = {};
226
- let animationProgress = 0;
227
- let isAnimating = false;
228
-
229
- // Create graph based on type
230
- function createGraph(type, nodeCount) {
231
- console.log(`Creating graph: type=${type}, nodeCount=${nodeCount}`);
232
- let graph;
233
- switch (type) {
234
- case 'random':
235
- graph = randomGraph(nodeCount, 0.15, Date.now());
236
- break;
237
- case 'scalefree':
238
- graph = scaleFreeGraph(nodeCount, 2, Date.now());
239
- break;
240
- case 'complete':
241
- graph = completeGraph(Math.min(nodeCount, 10)); // Limit complete graph size
242
- break;
243
- case 'bipartite':
244
- const n1 = Math.floor(nodeCount / 2);
245
- const n2 = nodeCount - n1;
246
- graph = bipartiteGraph(n1, n2, 0.3, Date.now());
247
- break;
248
- case 'grid':
249
- const size = Math.floor(Math.sqrt(nodeCount));
250
- graph = gridGraph(size, size);
251
- break;
252
- default:
253
- graph = randomGraph(nodeCount, 0.15, Date.now());
254
- }
255
-
256
- console.log(`Graph created:`, {
257
- nodes: graph.nodes?.()?.length || 'no nodes method',
258
- edges: graph.edges?.()?.length || 'no edges method',
259
- nodesList: graph.nodes?.() || 'no nodes',
260
- edgesList: graph.edges?.() || 'no edges'
261
- });
262
-
263
- return graph;
264
- }
265
-
266
- // Get layout based on algorithm
267
- function getLayout(algorithm, graph, params) {
268
- console.log(`Getting layout: algorithm=${algorithm}, params=`, params);
269
- const center = [0, 0, 0];
270
- const dim = 3;
271
-
272
- let positions;
273
- switch (algorithm) {
274
- case 'spring':
275
- console.log('Calling springLayout with params:', {
276
- springConstant: params.springConstant,
277
- iterations: params.iterations,
278
- center,
279
- dim
280
- });
281
- positions = springLayout(
282
- graph,
283
- params.springConstant,
284
- null,
285
- null,
286
- params.iterations,
287
- 1,
288
- center,
289
- dim
290
- );
291
- break;
292
- case 'forceatlas2':
293
- positions = forceatlas2Layout(
294
- graph,
295
- null,
296
- params.iterations,
297
- 1.0,
298
- 2.0,
299
- params.gravity,
300
- false,
301
- false,
302
- null,
303
- null,
304
- null,
305
- false,
306
- false,
307
- null,
308
- dim
309
- );
310
- break;
311
- case 'arf':
312
- positions = arfLayout(
313
- graph,
314
- null,
315
- 1,
316
- 1.1,
317
- params.iterations
318
- );
319
- break;
320
- default:
321
- positions = springLayout(graph, 1, null, null, 50, 1, center, dim);
322
- }
323
-
324
- console.log('Layout positions generated:', positions);
325
- console.log('Number of positioned nodes:', Object.keys(positions).length);
326
-
327
- return positions;
328
- }
329
-
330
- // Create force particles
331
- function createForceParticles(positions) {
332
- particleGroup.clear();
333
-
334
- if (!document.getElementById('showParticles').checked) return;
335
-
336
- const particleGeometry = new THREE.SphereGeometry(2, 8, 8);
337
- const particleMaterial = new THREE.MeshBasicMaterial({
338
- color: 0xffff00,
339
- transparent: true,
340
- opacity: 0.6
341
- });
342
-
343
- const scale = 200; // Same scale as nodes
344
- Object.values(positions).forEach(pos => {
345
- for (let i = 0; i < 3; i++) {
346
- const particle = new THREE.Mesh(particleGeometry, particleMaterial);
347
- const offset = (Math.random() - 0.5) * 50;
348
- particle.position.set(
349
- pos[0] * scale + offset,
350
- pos[1] * scale + offset,
351
- pos[2] * scale + offset
352
- );
353
- particleGroup.add(particle);
354
- }
355
- });
356
- }
357
-
358
- // Visualize graph
359
- function visualizeGraph(graph, positions, animate = true) {
360
- console.log('Visualizing graph with positions:', positions);
361
- const nodes = graph.nodes();
362
- const edges = graph.edges();
363
- console.log(`Visualizing: ${nodes.length} nodes, ${edges.length} edges`);
364
-
365
- // Store target positions
366
- targetPositions = { ...positions };
367
-
368
- // Initialize current positions if needed
369
- if (Object.keys(currentPositions).length === 0 || !animate) {
370
- currentPositions = { ...targetPositions };
371
- updateVisualization(graph, currentPositions);
372
- } else {
373
- // Start animation
374
- animationProgress = 0;
375
- isAnimating = true;
376
- }
377
-
378
- createForceParticles(positions);
379
- }
380
-
381
- // Update visualization with current positions
382
- function updateVisualization(graph, positions) {
383
- console.log('updateVisualization called with positions:', positions);
384
- // Clear previous visualization
385
- nodeGroup.clear();
386
- edgeGroup.clear();
387
-
388
- const nodes = graph.nodes();
389
- const edges = graph.edges();
390
- const nodeMeshes = {};
391
-
392
- console.log(`Creating visualization for ${nodes.length} nodes and ${edges.length} edges`);
393
-
394
- // Calculate node sizes based on degree
395
- const degrees = {};
396
- nodes.forEach(node => degrees[node] = 0);
397
- edges.forEach(([u, v]) => {
398
- degrees[u]++;
399
- degrees[v]++;
400
- });
401
-
402
- const maxDegree = Math.max(...Object.values(degrees), 1);
403
-
404
- // Create nodes
405
- nodes.forEach((node, i) => {
406
- const degree = degrees[node];
407
- const size = 5 + (degree / maxDegree) * 15;
408
-
409
- const geometry = new THREE.SphereGeometry(size, 32, 16);
410
- const color = new THREE.Color();
411
- color.setHSL(i / nodes.length, 0.7, 0.5);
412
-
413
- const material = new THREE.MeshPhongMaterial({
414
- color: color,
415
- emissive: color,
416
- emissiveIntensity: 0.3,
417
- shininess: 100
418
- });
419
-
420
- const mesh = new THREE.Mesh(geometry, material);
421
- const pos = positions[node] || [0, 0, 0];
422
- // Scale up positions for better visibility in 3D space
423
- const scale = 200;
424
- const scaledPos = [pos[0] * scale, pos[1] * scale, pos[2] * scale];
425
- console.log(`Node ${node} positioned at [${pos[0]}, ${pos[1]}, ${pos[2]}] -> scaled to [${scaledPos[0]}, ${scaledPos[1]}, ${scaledPos[2]}]`);
426
- mesh.position.set(scaledPos[0], scaledPos[1], scaledPos[2]);
427
-
428
- nodeGroup.add(mesh);
429
- nodeMeshes[node] = mesh;
430
- });
431
-
432
- console.log(`Added ${nodeGroup.children.length} node meshes to scene`);
433
-
434
- // Create edges
435
- const scale = 200; // Same scale as nodes
436
- edges.forEach(([source, target]) => {
437
- const sourcePos = positions[source] || [0, 0, 0];
438
- const targetPos = positions[target] || [0, 0, 0];
439
-
440
- const points = [
441
- new THREE.Vector3(sourcePos[0] * scale, sourcePos[1] * scale, sourcePos[2] * scale),
442
- new THREE.Vector3(targetPos[0] * scale, targetPos[1] * scale, targetPos[2] * scale)
443
- ];
444
-
445
- const geometry = new THREE.BufferGeometry().setFromPoints(points);
446
- const material = new THREE.LineBasicMaterial({
447
- color: 0x4466ff,
448
- transparent: true,
449
- opacity: 0.4
450
- });
451
-
452
- const line = new THREE.Line(geometry, material);
453
- edgeGroup.add(line);
454
- });
455
- }
456
-
457
- // Initial setup
458
- let currentGraph = createGraph('random', 25);
459
- let currentAlgorithm = 'spring';
460
-
461
- // Controls
462
- const algorithmSelect = document.getElementById('algorithm');
463
- const graphTypeSelect = document.getElementById('graphType');
464
- const nodeCountSlider = document.getElementById('nodeCount');
465
- const iterationsSlider = document.getElementById('iterations');
466
- const springConstantSlider = document.getElementById('springConstant');
467
- const gravitySlider = document.getElementById('gravity');
468
- const attractionSlider = document.getElementById('attraction');
469
- const animateCheckbox = document.getElementById('animateLayout');
470
-
471
- // Update parameter visibility based on algorithm
472
- function updateParameterVisibility() {
473
- const algorithm = algorithmSelect.value;
474
- document.getElementById('springConstantControl').style.display =
475
- (algorithm === 'spring' || algorithm === 'arf') ? 'block' : 'none';
476
- document.getElementById('gravityControl').style.display =
477
- algorithm === 'forceatlas2' ? 'block' : 'none';
478
- document.getElementById('attractionControl').style.display =
479
- algorithm === 'arf' ? 'block' : 'none';
480
- }
481
-
482
- // Event listeners
483
- algorithmSelect.addEventListener('change', () => {
484
- currentAlgorithm = algorithmSelect.value;
485
- updateParameterVisibility();
486
- applyLayout();
487
- });
488
-
489
- nodeCountSlider.addEventListener('input', (e) => {
490
- document.getElementById('nodeCountLabel').textContent = e.target.value;
491
- });
492
-
493
- iterationsSlider.addEventListener('input', (e) => {
494
- document.getElementById('iterationsLabel').textContent = e.target.value;
495
- });
496
-
497
- springConstantSlider.addEventListener('input', (e) => {
498
- document.getElementById('springLabel').textContent = e.target.value;
499
- });
500
-
501
- gravitySlider.addEventListener('input', (e) => {
502
- document.getElementById('gravityLabel').textContent = e.target.value;
503
- });
504
-
505
- attractionSlider.addEventListener('input', (e) => {
506
- document.getElementById('attractionLabel').textContent = e.target.value;
507
- });
508
-
509
- document.getElementById('regenerate').addEventListener('click', () => {
510
- const type = graphTypeSelect.value;
511
- const nodeCount = parseInt(nodeCountSlider.value);
512
- currentGraph = createGraph(type, nodeCount);
513
- applyLayout();
514
- });
515
-
516
- document.getElementById('showParticles').addEventListener('change', () => {
517
- if (!document.getElementById('showParticles').checked) {
518
- particleGroup.clear();
519
- } else {
520
- createForceParticles(targetPositions);
521
- }
522
- });
523
-
524
- // Apply layout with current parameters
525
- function applyLayout() {
526
- const params = {
527
- iterations: parseInt(iterationsSlider.value),
528
- springConstant: parseFloat(springConstantSlider.value),
529
- gravity: parseFloat(gravitySlider.value),
530
- attraction: parseFloat(attractionSlider.value)
531
- };
532
-
533
- const positions = getLayout(currentAlgorithm, currentGraph, params);
534
- visualizeGraph(currentGraph, positions, animateCheckbox.checked);
535
- }
536
-
537
- // Smooth position interpolation
538
- function lerp(start, end, t) {
539
- return start + (end - start) * t;
540
- }
541
-
542
- function easeInOutCubic(t) {
543
- return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
544
- }
545
-
546
- // Animation loop
547
- function animate() {
548
- requestAnimationFrame(animate);
549
-
550
- // Animate layout transition
551
- if (isAnimating && animateCheckbox.checked) {
552
- animationProgress += 0.02;
553
- if (animationProgress >= 1) {
554
- animationProgress = 1;
555
- isAnimating = false;
556
- currentPositions = { ...targetPositions };
557
- }
558
-
559
- const t = easeInOutCubic(animationProgress);
560
- const interpolatedPositions = {};
561
-
562
- for (const node in targetPositions) {
563
- const current = currentPositions[node] || [0, 0, 0];
564
- const target = targetPositions[node];
565
- interpolatedPositions[node] = [
566
- lerp(current[0], target[0], t),
567
- lerp(current[1], target[1], t),
568
- lerp(current[2], target[2], t)
569
- ];
570
- }
571
-
572
- updateVisualization(currentGraph, interpolatedPositions);
573
- }
574
-
575
- // Animate particles
576
- particleGroup.children.forEach((particle, i) => {
577
- particle.position.y += Math.sin(Date.now() * 0.001 + i) * 0.5;
578
- particle.material.opacity = 0.3 + Math.sin(Date.now() * 0.002 + i) * 0.3;
579
- });
580
-
581
- controls.update();
582
- renderer.render(scene, camera);
583
- }
584
-
585
- // Handle window resize
586
- window.addEventListener('resize', () => {
587
- camera.aspect = window.innerWidth / window.innerHeight;
588
- camera.updateProjectionMatrix();
589
- renderer.setSize(window.innerWidth, window.innerHeight);
590
- });
591
-
592
- // Initialize
593
- console.log('Initializing application...');
594
- updateParameterVisibility();
595
- console.log('About to call applyLayout()...');
596
- applyLayout();
597
- console.log('Starting animation loop...');
598
- animate();
599
-
600
- } catch (error) {
601
- console.error('Error during setup:', error);
602
- console.error('Error stack:', error.stack);
603
- // Show error on page
604
- const errorDiv = document.createElement('div');
605
- errorDiv.style.cssText = 'position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(255,0,0,0.9); color: white; padding: 20px; border-radius: 10px; z-index: 1000; max-width: 80%; text-align: center;';
606
- errorDiv.innerHTML = '<h3>Error Loading 3D Demo</h3><p>' + error.message + '</p><p style="font-size: 12px;">' + error.stack + '</p>';
607
- document.body.appendChild(errorDiv);
608
- }
609
- </script>
610
- </body>
611
- </html>