175 lines
6.1 KiB
PHP
175 lines
6.1 KiB
PHP
<?php
|
|
$page_title = "Federated Learning";
|
|
include 'includes/header.php';
|
|
?>
|
|
<style>
|
|
.federated-container {
|
|
position: relative;
|
|
width: 100%;
|
|
height: 400px;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
.fl-node {
|
|
position: absolute;
|
|
width: 120px;
|
|
height: 120px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
align-items: center;
|
|
text-align: center;
|
|
padding: 10px;
|
|
font-size: 0.9rem;
|
|
box-shadow: var(--shadow-sm);
|
|
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
}
|
|
.fl-node.central {
|
|
width: 160px;
|
|
height: 160px;
|
|
background-color: var(--primary);
|
|
color: white;
|
|
z-index: 10;
|
|
font-size: 1.1rem;
|
|
}
|
|
.fl-node.local {
|
|
background-color: var(--card-bg);
|
|
color: var(--text-primary);
|
|
}
|
|
.fl-line {
|
|
position: absolute;
|
|
background-color: #ccc;
|
|
z-index: 1;
|
|
transform-origin: 0 0;
|
|
transition: background-color 0.5s ease;
|
|
}
|
|
.fl-line.animated {
|
|
background-color: var(--accent);
|
|
}
|
|
</style>
|
|
|
|
<div class="container mt-5">
|
|
<h1 class="page-title">Federated Learning Simulation</h1>
|
|
<p class="lead mb-5">Visualizing how distributed learning improves the national model without sharing sensitive data.</p>
|
|
|
|
<div class="row g-4">
|
|
<div class="col-lg-8">
|
|
<div class="card h-100">
|
|
<div class="card-body">
|
|
<div id="federated-visualization" class="federated-container"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-4">
|
|
<div class="card h-100">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">Simulation Log</h5>
|
|
</div>
|
|
<div id="log-container" class="card-body" style="max-height: 350px; overflow-y: auto; font-family: 'Roboto Mono', monospace; font-size: 0.85rem;">
|
|
<p class="text-muted">Click the button to start the simulation...</p>
|
|
</div>
|
|
<div class="card-footer text-center">
|
|
<button id="trigger-round" class="btn btn-primary">Trigger New Aggregation Round</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const container = document.getElementById('federated-visualization');
|
|
const logContainer = document.getElementById('log-container');
|
|
const triggerButton = document.getElementById('trigger-round');
|
|
let round = 0;
|
|
|
|
const nodes = [
|
|
{ id: 'central', name: 'National Model', isCentral: true },
|
|
{ id: 'node1', name: 'Mumbai Substation', angle: 0 },
|
|
{ id: 'node2', name: 'Delhi Substation', angle: 72 },
|
|
{ id: 'node3', name: 'Chennai Substation', angle: 144 },
|
|
{ id: 'node4', name: 'Kolkata Substation', angle: 216 },
|
|
{ id: 'node5', name: 'Bangalore Substation', angle: 288 },
|
|
];
|
|
|
|
const radius = 150;
|
|
const containerWidth = container.offsetWidth;
|
|
const containerHeight = container.offsetHeight;
|
|
const centerX = containerWidth / 2;
|
|
const centerY = containerHeight / 2;
|
|
|
|
// Create nodes and lines
|
|
nodes.forEach(node => {
|
|
const nodeEl = document.createElement('div');
|
|
nodeEl.id = node.id;
|
|
nodeEl.className = `fl-node ${node.isCentral ? 'central' : 'local'}`;
|
|
nodeEl.innerHTML = `<strong>${node.name}</strong>`;
|
|
if (node.isCentral) {
|
|
nodeEl.style.left = `${centerX - 80}px`;
|
|
nodeEl.style.top = `${centerY - 80}px`;
|
|
} else {
|
|
const x = centerX + radius * Math.cos(node.angle * Math.PI / 180) - 60;
|
|
const y = centerY + radius * Math.sin(node.angle * Math.PI / 180) - 60;
|
|
nodeEl.style.left = `${x}px`;
|
|
nodeEl.style.top = `${y}px`;
|
|
|
|
// Draw line from local to central
|
|
const line = document.createElement('div');
|
|
line.id = `line-${node.id}`;
|
|
line.className = 'fl-line';
|
|
const angle = Math.atan2(y - centerY, x - centerX) * 180 / Math.PI;
|
|
const length = Math.sqrt(Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2));
|
|
line.style.width = `${length}px`;
|
|
line.style.height = '2px';
|
|
line.style.left = `${centerX}px`;
|
|
line.style.top = `${centerY}px`;
|
|
line.style.transform = `rotate(${angle}deg)`;
|
|
container.appendChild(line);
|
|
}
|
|
container.appendChild(nodeEl);
|
|
});
|
|
|
|
function log(message, type = 'info') {
|
|
const colors = { info: 'text-secondary', success: 'text-success', emphasis: 'text-primary' };
|
|
logContainer.innerHTML += `<p class="mb-1 ${colors[type]}">[${new Date().toLocaleTimeString()}] ${message}</p>`;
|
|
logContainer.scrollTop = logContainer.scrollHeight;
|
|
}
|
|
|
|
triggerButton.addEventListener('click', function() {
|
|
if (round === 0) {
|
|
logContainer.innerHTML = '';
|
|
}
|
|
round++;
|
|
log(`Starting Aggregation Round ${round}...`, 'emphasis');
|
|
this.disabled = true;
|
|
|
|
let delay = 500;
|
|
nodes.filter(n => !n.isCentral).forEach(node => {
|
|
setTimeout(() => {
|
|
log(`Node ${node.name} sending model updates...`);
|
|
document.getElementById(`line-${node.id}`).classList.add('animated');
|
|
}, delay);
|
|
delay += 300;
|
|
});
|
|
|
|
setTimeout(() => {
|
|
log('All updates received. Aggregating national model...');
|
|
const centralNode = document.getElementById('central');
|
|
centralNode.style.transform = 'scale(1.1)';
|
|
}, delay + 500);
|
|
|
|
setTimeout(() => {
|
|
log(`Round ${round} complete. National model updated.`, 'success');
|
|
document.getElementById('central').style.transform = 'scale(1)';
|
|
nodes.filter(n => !n.isCentral).forEach(node => {
|
|
document.getElementById(`line-${node.id}`).classList.remove('animated');
|
|
});
|
|
this.disabled = false;
|
|
}, delay + 1500);
|
|
});
|
|
});
|
|
</script>
|
|
|
|
<?php include 'includes/footer.php'; ?>
|