Show code
```html
<!DOCTYPE html>
<html>
<head>
<title>Force Simulation Graph</title>
<style>
body {margin:0;overflow:hidden;background:#111;}canvas {display:block;border:1px solid #333;}</style>
</head>
<body>
<canvas id="graphCanvas"></canvas>
<script>
(function() {
const canvas = document.getElementById('graphCanvas');
const ctx = canvas.getContext('2d');
let W, H;
let N = 25;
let nodes = [];
let edges = [];
let isRunning = true;
let FRAME_COUNT = 0;
// Physics constants
const K_SPRING = 0.01; // Spring constant (Attraction)
const REPULSION_COEFF = 2000; // Repulsion strength
const DAMPING = 0.98; // Velocity damping factor
const MAX_VELOCITY = 3;
function resizeCanvas() {
W = canvas.width = window.innerWidth * 0.95;
H = canvas.height = window.innerHeight * 0.85;
}
function initGraph() {
nodes = [];
edges = [];
for (let i = 0; i < N; i++) {
nodes.push({ x: Math.random() * W, y: Math.random() * H, vx: 0, vy: 0, fx: 0, fy: 0 });
}
// Generate edges (fully connected graph)
edges = [];
for (let i = 0; i < N; i++) {
for (let j = i + 1; j < N; j++) {
edges.push({ source: i, target: j });
}
}
isRunning = true;
}
function calculateForces() {
// Reset forces
nodes.forEach(n => { n.fx = 0; n.fy = 0; });
let totalEnergy = 0;
// 1. Calculate Repulsion (All pairs)
for (let i = 0; i < N; i++) {
for (let j = i + 1; j < N; j++) {
const n1 = nodes[i];
const n2 = nodes[j];
const dx = n1.x - n2.x;
const dy = n1.y - n2.y;
let distSq = dx * dx + dy * dy;
let dist = Math.sqrt(distSq);
if (dist === 0) { dist = 0.001; }
// Force magnitude: Inverse square law repulsion
const force = REPULSION_COEFF / distSq;
n1.fx += (dx / dist) * force;
n1.fy += (dy / dist) * force;
n2.fx -= (dx / dist) * force;
n2.fy -= (dy / dist) * force;
}
}
// 2. Calculate Attraction (Springs/Edges)
for (const edge of edges) {
const n1 = nodes[edge.source];
const n2 = nodes[edge.target];
const dx = n1.x - n2.x;
const dy = n1.y - n2.y;
let distSq = dx * dx + dy * dy;
let dist = Math.sqrt(distSq);
if (dist === 0) { dist = 0.001; }
// Spring force: F = k * x (Hooke's Law, using current distance for approximation)
const idealLength = W / N * 0.5; // Simple estimate for desired length
let displacement = Math.abs(dist) - idealLength;
let forceMagnitude = K_SPRING * displacement;
// Apply force components
const fX = (dx / dist) * forceMagnitude;
const fY = (dy / dist) * forceMagnitude;
n1.fx += fX;
n1.fy += fY;
n2.fx -= fX;
n2.fy -= fY;
}
// 3. Update positions and velocities (Integration)
for (let i = 0; i < N; i++) {
const n = nodes[i];
// Apply forces to acceleration proxy
n.vx += n.fx * 0.1;
n.vy += n.fy * 0.1;
// Apply damping and limit velocity
n.vx *= DAMPING;
n.vy *= DAMPING;
const speed = Math.sqrt(n.vx * n.vx + n.vy * n.vy);
if (speed > MAX_VELOCITY) {
n.vx = (n.vx / speed) * MAX_VELOCITY;
n.vy = (n.vy / speed) * MAX_VELOCITY;
}
// Update position
n.x += n.vx;
n.y += n.vy;
}
}
function draw() {
ctx.fillStyle = 'rgba(17, 17, 17, 0.4)';
ctx.fillRect(0, 0, W, H); // Fade effect for trailing motion
// Draw Edges
ctx.strokeStyle = '#666';
ctx.lineWidth = 1.5;
for (const edge of edges) {
const n1 = nodes[edge.source];
const n2 = nodes[edge.target];
ctx.beginPath();
ctx.moveTo(n1.x, n1.y);
ctx.lineTo(n2.x, n2.y);
ctx.stroke();
}
// Draw Nodes and Labels
for (let i = 0; i < N; i++) {
const n = nodes[i];
// Node circle
ctx.fillStyle = '#4CAF50';
ctx.beginPath();
ctx.arc(n.x, n.y, 3, 0, Math.PI * 2);
ctx.fill();
// Label
ctx.fillStyle = '#FFF';
ctx.font = '10px Arial';
ctx.fillText(`N${i}`, n.x + 5, n.y - 5);
}
}
function updateMetrics() {
const totalEnergy = Math.abs(nodes.reduce((sum, n) => sum + (n.fx * n.vx + n.fy * n.vy), 0));
const nodeCountText = `Nodes: ${N}`;
const energyText = `Energy Est.: ${totalEnergy.toFixed(2)}`;
const infoDiv = document.createElement('div');
infoDiv.style.position = 'absolute';
infoDiv.style.top = '10px';
infoDiv.style.left = '10px';
infoDiv.style.color = '#CCC';
infoDiv.innerHTML = `${nodeCountText}<br>${energyText}`;
let infoContainer = document.getElementById('info');
if (!infoContainer) {
infoContainer = document.createElement('div');
infoContainer.id = 'info';
document.body.appendChild(infoContainer);
}
infoContainer.innerHTML = `${nodeCountText}<br>${energyText}`;
}
function loop() {
if (!isRunning) {
// Check for settlement: if average speed is very low, restart
const avgSpeedSq = nodes.reduce((sum, n) => sum + (n.vx * n.vx + n.vy * n.vy), 0) / N;
if (avgSpeedSq < 0.01 && FRAME_COUNT > 300) { // Stay settled for a moment before restarting
initGraph();
FRAME_COUNT = 0;
} else if (Math.random() < 0.005) { // Small chance to restart even if settling slowly
initGraph();
FRAME_COUNT = 0;
}
}
calculateForces();
draw();
updateMetrics();
requestAnimationFrame(loop);
FRAME_COUNT++;
}
window.addEventListener('resize', () => {
resizeCanvas();
// Re-run init to keep nodes within bounds if resizing drastically changes W/H
nodes = [];
edges = [];
initGraph();
});
resizeCanvas();
initGraph();
loop();
})();
</script>
</body>
</html>
```