@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.
- 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 +340 -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 +194 -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 +19 -4
- package/src/layouts/force-directed/forceatlas2.ts +23 -4
- package/src/utils/rescale.ts +90 -15
- 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,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>
|