@graphty/layout 1.2.3 → 1.2.5

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 (203) hide show
  1. package/dist/src/algorithms/index.d.ts +5 -0
  2. package/dist/src/algorithms/index.js +6 -0
  3. package/dist/src/algorithms/index.js.map +1 -0
  4. package/dist/src/algorithms/optimization/index.d.ts +7 -0
  5. package/dist/src/algorithms/optimization/index.js +7 -0
  6. package/dist/src/algorithms/optimization/index.js.map +1 -0
  7. package/dist/src/algorithms/optimization/kamada-kawai-solver.d.ts +32 -0
  8. package/dist/src/algorithms/optimization/kamada-kawai-solver.js +176 -0
  9. package/dist/src/algorithms/optimization/kamada-kawai-solver.js.map +1 -0
  10. package/dist/src/algorithms/optimization/lbfgs.d.ts +13 -0
  11. package/dist/src/algorithms/optimization/lbfgs.js +56 -0
  12. package/dist/src/algorithms/optimization/lbfgs.js.map +1 -0
  13. package/dist/src/algorithms/optimization/line-search.d.ts +15 -0
  14. package/dist/src/algorithms/optimization/line-search.js +37 -0
  15. package/dist/src/algorithms/optimization/line-search.js.map +1 -0
  16. package/dist/src/algorithms/optimization/types.d.ts +5 -0
  17. package/dist/src/algorithms/optimization/types.js +5 -0
  18. package/dist/src/algorithms/optimization/types.js.map +1 -0
  19. package/dist/src/algorithms/planarity/check.d.ts +17 -0
  20. package/dist/src/algorithms/planarity/check.js +30 -0
  21. package/dist/src/algorithms/planarity/check.js.map +1 -0
  22. package/dist/src/algorithms/planarity/embedding.d.ts +29 -0
  23. package/dist/src/algorithms/planarity/embedding.js +179 -0
  24. package/dist/src/algorithms/planarity/embedding.js.map +1 -0
  25. package/dist/src/algorithms/planarity/index.d.ts +7 -0
  26. package/dist/src/algorithms/planarity/index.js +8 -0
  27. package/dist/src/algorithms/planarity/index.js.map +1 -0
  28. package/dist/src/algorithms/planarity/lr-test.d.ts +15 -0
  29. package/dist/src/algorithms/planarity/lr-test.js +54 -0
  30. package/dist/src/algorithms/planarity/lr-test.js.map +1 -0
  31. package/dist/src/algorithms/planarity/special-graphs.d.ts +28 -0
  32. package/dist/src/algorithms/planarity/special-graphs.js +108 -0
  33. package/dist/src/algorithms/planarity/special-graphs.js.map +1 -0
  34. package/dist/src/generators/basic.d.ts +28 -0
  35. package/dist/src/generators/basic.js +79 -0
  36. package/dist/src/generators/basic.js.map +1 -0
  37. package/dist/src/generators/bipartite.d.ts +16 -0
  38. package/dist/src/generators/bipartite.js +40 -0
  39. package/dist/src/generators/bipartite.js.map +1 -0
  40. package/dist/src/generators/grid.d.ts +11 -0
  41. package/dist/src/generators/grid.js +37 -0
  42. package/dist/src/generators/grid.js.map +1 -0
  43. package/dist/src/generators/index.d.ts +8 -0
  44. package/dist/src/generators/index.js +9 -0
  45. package/dist/src/generators/index.js.map +1 -0
  46. package/dist/src/generators/random.d.ts +12 -0
  47. package/dist/src/generators/random.js +34 -0
  48. package/dist/src/generators/random.js.map +1 -0
  49. package/dist/src/generators/scale-free.d.ts +12 -0
  50. package/dist/src/generators/scale-free.js +62 -0
  51. package/dist/src/generators/scale-free.js.map +1 -0
  52. package/dist/src/index.d.ts +10 -0
  53. package/dist/src/index.js +15 -0
  54. package/dist/src/index.js.map +1 -0
  55. package/dist/src/layouts/basic/index.d.ts +4 -0
  56. package/dist/src/layouts/basic/index.js +5 -0
  57. package/dist/src/layouts/basic/index.js.map +1 -0
  58. package/dist/src/layouts/basic/random.d.ts +14 -0
  59. package/dist/src/layouts/basic/random.js +27 -0
  60. package/dist/src/layouts/basic/random.js.map +1 -0
  61. package/dist/src/layouts/force-directed/arf.d.ts +13 -0
  62. package/dist/src/layouts/force-directed/arf.js +103 -0
  63. package/dist/src/layouts/force-directed/arf.js.map +1 -0
  64. package/dist/src/layouts/force-directed/forceatlas2.d.ts +22 -0
  65. package/dist/src/layouts/force-directed/forceatlas2.js +340 -0
  66. package/dist/src/layouts/force-directed/forceatlas2.js.map +1 -0
  67. package/dist/src/layouts/force-directed/fruchterman-reingold.d.ts +19 -0
  68. package/dist/src/layouts/force-directed/fruchterman-reingold.js +131 -0
  69. package/dist/src/layouts/force-directed/fruchterman-reingold.js.map +1 -0
  70. package/dist/src/layouts/force-directed/index.d.ts +8 -0
  71. package/dist/src/layouts/force-directed/index.js +9 -0
  72. package/dist/src/layouts/force-directed/index.js.map +1 -0
  73. package/dist/src/layouts/force-directed/kamada-kawai.d.ts +15 -0
  74. package/dist/src/layouts/force-directed/kamada-kawai.js +87 -0
  75. package/dist/src/layouts/force-directed/kamada-kawai.js.map +1 -0
  76. package/dist/src/layouts/force-directed/spring.d.ts +19 -0
  77. package/dist/src/layouts/force-directed/spring.js +23 -0
  78. package/dist/src/layouts/force-directed/spring.js.map +1 -0
  79. package/dist/src/layouts/geometric/circular.d.ts +14 -0
  80. package/dist/src/layouts/geometric/circular.js +67 -0
  81. package/dist/src/layouts/geometric/circular.js.map +1 -0
  82. package/dist/src/layouts/geometric/index.d.ts +6 -0
  83. package/dist/src/layouts/geometric/index.js +7 -0
  84. package/dist/src/layouts/geometric/index.js.map +1 -0
  85. package/dist/src/layouts/geometric/shell.d.ts +15 -0
  86. package/dist/src/layouts/geometric/shell.js +67 -0
  87. package/dist/src/layouts/geometric/shell.js.map +1 -0
  88. package/dist/src/layouts/geometric/spiral.d.ts +16 -0
  89. package/dist/src/layouts/geometric/spiral.js +73 -0
  90. package/dist/src/layouts/geometric/spiral.js.map +1 -0
  91. package/dist/src/layouts/hierarchical/bfs.d.ts +12 -0
  92. package/dist/src/layouts/hierarchical/bfs.js +62 -0
  93. package/dist/src/layouts/hierarchical/bfs.js.map +1 -0
  94. package/dist/src/layouts/hierarchical/bipartite.d.ts +16 -0
  95. package/dist/src/layouts/hierarchical/bipartite.js +72 -0
  96. package/dist/src/layouts/hierarchical/bipartite.js.map +1 -0
  97. package/dist/src/layouts/hierarchical/index.d.ts +6 -0
  98. package/dist/src/layouts/hierarchical/index.js +7 -0
  99. package/dist/src/layouts/hierarchical/index.js.map +1 -0
  100. package/dist/src/layouts/hierarchical/multipartite.d.ts +12 -0
  101. package/dist/src/layouts/hierarchical/multipartite.js +73 -0
  102. package/dist/src/layouts/hierarchical/multipartite.js.map +1 -0
  103. package/dist/src/layouts/index.d.ts +8 -0
  104. package/dist/src/layouts/index.js +9 -0
  105. package/dist/src/layouts/index.js.map +1 -0
  106. package/dist/src/layouts/specialized/index.d.ts +5 -0
  107. package/dist/src/layouts/specialized/index.js +6 -0
  108. package/dist/src/layouts/specialized/index.js.map +1 -0
  109. package/dist/src/layouts/specialized/planar.d.ts +14 -0
  110. package/dist/src/layouts/specialized/planar.js +47 -0
  111. package/dist/src/layouts/specialized/planar.js.map +1 -0
  112. package/dist/src/layouts/specialized/spectral.d.ts +14 -0
  113. package/dist/src/layouts/specialized/spectral.js +106 -0
  114. package/dist/src/layouts/specialized/spectral.js.map +1 -0
  115. package/dist/src/types/embedding.d.ts +9 -0
  116. package/dist/src/types/embedding.js +5 -0
  117. package/dist/src/types/embedding.js.map +1 -0
  118. package/dist/src/types/graph.d.ts +10 -0
  119. package/dist/src/types/graph.js +5 -0
  120. package/dist/src/types/graph.js.map +1 -0
  121. package/dist/src/types/index.d.ts +6 -0
  122. package/dist/src/types/index.js +7 -0
  123. package/dist/src/types/index.js.map +1 -0
  124. package/dist/src/types/layout.d.ts +6 -0
  125. package/dist/src/types/layout.js +5 -0
  126. package/dist/src/types/layout.js.map +1 -0
  127. package/dist/src/utils/graph.d.ts +34 -0
  128. package/dist/src/utils/graph.js +69 -0
  129. package/dist/src/utils/graph.js.map +1 -0
  130. package/dist/src/utils/index.d.ts +8 -0
  131. package/dist/src/utils/index.js +9 -0
  132. package/dist/src/utils/index.js.map +1 -0
  133. package/dist/src/utils/numpy.d.ts +17 -0
  134. package/dist/src/utils/numpy.js +101 -0
  135. package/dist/src/utils/numpy.js.map +1 -0
  136. package/dist/src/utils/params.d.ts +17 -0
  137. package/dist/src/utils/params.js +22 -0
  138. package/dist/src/utils/params.js.map +1 -0
  139. package/dist/src/utils/random.d.ts +14 -0
  140. package/dist/src/utils/random.js +42 -0
  141. package/dist/src/utils/random.js.map +1 -0
  142. package/dist/src/utils/rescale.d.ts +21 -0
  143. package/dist/src/utils/rescale.js +194 -0
  144. package/dist/src/utils/rescale.js.map +1 -0
  145. package/dist/vitest.config.js +1 -1
  146. package/dist/vitest.config.js.map +1 -1
  147. package/package.json +19 -4
  148. package/src/layouts/force-directed/forceatlas2.ts +23 -4
  149. package/src/utils/rescale.ts +90 -15
  150. package/.env.example +0 -11
  151. package/.github/workflows/ci.yml +0 -101
  152. package/.releaserc.json +0 -22
  153. package/CHANGELOG.md +0 -52
  154. package/CLAUDE.md +0 -104
  155. package/CONTRIBUTING.md +0 -1
  156. package/DEPLOYMENT.md +0 -59
  157. package/build-examples-script.js +0 -26
  158. package/commitlint.config.js +0 -37
  159. package/examples/3d-force-directed.html +0 -611
  160. package/examples/3d-kamada-kawai.html +0 -379
  161. package/examples/3d-layout-comparison.html +0 -448
  162. package/examples/3d-spherical-layout.html +0 -319
  163. package/examples/arf-layout.html +0 -524
  164. package/examples/bfs-layout.html +0 -487
  165. package/examples/bipartite-layout.html +0 -382
  166. package/examples/circular-layout.html +0 -292
  167. package/examples/forceatlas2-layout.html +0 -588
  168. package/examples/index.html +0 -241
  169. package/examples/kamada-kawai-layout.html +0 -425
  170. package/examples/layout-helpers.js +0 -23
  171. package/examples/layout.js +0 -2304
  172. package/examples/multipartite-layout.html +0 -410
  173. package/examples/planar-layout.html +0 -388
  174. package/examples/random-layout.html +0 -328
  175. package/examples/shell-layout.html +0 -323
  176. package/examples/spectral-layout.html +0 -335
  177. package/examples/spiral-layout.html +0 -447
  178. package/examples/spring-layout.html +0 -313
  179. package/test/arf-layout.test.ts +0 -443
  180. package/test/bfs-layout.test.ts +0 -427
  181. package/test/bipartite-layout.test.ts +0 -344
  182. package/test/circular-layout.test.ts +0 -442
  183. package/test/forceatlas2-layout.test.ts +0 -405
  184. package/test/fruchterman-reingold-layout.test.ts +0 -477
  185. package/test/graph-generators.test.ts +0 -450
  186. package/test/kamada-kawai-layout.test.ts +0 -623
  187. package/test/multipartite-layout.test.ts +0 -404
  188. package/test/planar-layout.test.ts +0 -266
  189. package/test/random-layout.test.ts +0 -254
  190. package/test/rescale-layout.test.ts +0 -373
  191. package/test/shell-layout.test.ts +0 -347
  192. package/test/spectral-layout.test.ts +0 -378
  193. package/test/spiral-layout.test.ts +0 -338
  194. package/test/spring-layout.test.ts +0 -241
  195. package/test/test-utils.ts +0 -34
  196. package/test/utils-graph.test.ts +0 -155
  197. package/test/utils-index.test.ts +0 -77
  198. package/test/utils-numpy.test.ts +0 -272
  199. package/test/utils-params.test.ts +0 -99
  200. package/test/utils-random.test.ts +0 -229
  201. package/tsconfig.json +0 -16
  202. package/vite.config.js +0 -36
  203. 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>