feat(rnetwork): concentric spheres layout with Fibonacci distribution
Replace flat ring layout with 3D sphere distribution using Fibonacci spiral for even node placement. Wireframe sphere guides replace flat ring guides — visible from every camera angle. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
246b51b2e0
commit
03de21ddd5
|
|
@ -1075,9 +1075,9 @@ class FolkGraphViewer extends HTMLElement {
|
||||||
n.fx = 0; n.fy = 0; n.fz = 0;
|
n.fx = 0; n.fy = 0; n.fz = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.placeRing(adminNodes, 30);
|
this.placeSphere(adminNodes, 30);
|
||||||
this.placeRing(memberNodes, 80);
|
this.placeSphere(memberNodes, 80);
|
||||||
this.placeRing(viewerNodes, 160);
|
this.placeSphere(viewerNodes, 160);
|
||||||
|
|
||||||
this.graph.graphData(data);
|
this.graph.graphData(data);
|
||||||
this.graph.d3ReheatSimulation();
|
this.graph.d3ReheatSimulation();
|
||||||
|
|
@ -1087,14 +1087,18 @@ class FolkGraphViewer extends HTMLElement {
|
||||||
this.addRingGuides();
|
this.addRingGuides();
|
||||||
}
|
}
|
||||||
|
|
||||||
private placeRing(nodes: GraphNode[], radius: number) {
|
private placeSphere(nodes: GraphNode[], radius: number) {
|
||||||
const count = nodes.length;
|
const count = nodes.length;
|
||||||
if (count === 0) return;
|
if (count === 0) return;
|
||||||
|
// Fibonacci sphere — even distribution on a sphere surface
|
||||||
|
const goldenAngle = Math.PI * (3 - Math.sqrt(5));
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
const angle = (2 * Math.PI * i) / count;
|
const y = 1 - (2 * i) / (count - 1 || 1); // -1 to 1
|
||||||
nodes[i].fx = Math.cos(angle) * radius;
|
const r = Math.sqrt(1 - y * y);
|
||||||
nodes[i].fy = 0;
|
const theta = goldenAngle * i;
|
||||||
nodes[i].fz = Math.sin(angle) * radius;
|
nodes[i].fx = Math.cos(theta) * r * radius;
|
||||||
|
nodes[i].fy = y * radius;
|
||||||
|
nodes[i].fz = Math.sin(theta) * r * radius;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1116,23 +1120,22 @@ class FolkGraphViewer extends HTMLElement {
|
||||||
const scene = this.graph.scene();
|
const scene = this.graph.scene();
|
||||||
if (!scene) return;
|
if (!scene) return;
|
||||||
|
|
||||||
const ringRadii = [
|
const sphereRadii = [
|
||||||
{ r: 30, color: 0xa78bfa, label: "Admin" },
|
{ r: 30, color: 0xa78bfa, label: "Admin" },
|
||||||
{ r: 80, color: 0x10b981, label: "Member" },
|
{ r: 80, color: 0x10b981, label: "Member" },
|
||||||
{ r: 160, color: 0x3b82f6, label: "Viewer" },
|
{ r: 160, color: 0x3b82f6, label: "Viewer" },
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const { r, color } of ringRadii) {
|
for (const { r, color } of sphereRadii) {
|
||||||
const geometry = new THREE.RingGeometry(r - 0.5, r + 0.5, 128);
|
// Wireframe sphere guide
|
||||||
|
const geometry = new THREE.SphereGeometry(r, 24, 16);
|
||||||
const material = new THREE.MeshBasicMaterial({
|
const material = new THREE.MeshBasicMaterial({
|
||||||
color,
|
color,
|
||||||
transparent: true,
|
transparent: true,
|
||||||
opacity: 0.15,
|
opacity: 0.06,
|
||||||
side: THREE.DoubleSide,
|
wireframe: true,
|
||||||
});
|
});
|
||||||
const mesh = new THREE.Mesh(geometry, material);
|
const mesh = new THREE.Mesh(geometry, material);
|
||||||
mesh.rotation.x = -Math.PI / 2; // flat on XZ plane
|
|
||||||
mesh.position.y = 0;
|
|
||||||
scene.add(mesh);
|
scene.add(mesh);
|
||||||
this.ringGuides.push(mesh);
|
this.ringGuides.push(mesh);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue