Files
nodejs-visualiser/public/app.js
T

363 lines
10 KiB
JavaScript

const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const socket = new WebSocket(`${protocol}//${window.location.host}`);
socket.addEventListener('open', () => {
console.log('WebSocket connection established');
});
socket.addEventListener('close', () => {
console.log('WebSocket connection closed');
});
socket.addEventListener('error', (error) => {
console.error('WebSocket error:', error);
});
const nodes = new vis.DataSet();
const edges = new vis.DataSet();
const container = document.getElementById('network');
const data = {
nodes: nodes,
edges: edges
};
const DEFAULT_COLOR = '#e0e0e0'; // Very light grey
const MIN_COLOR_DISTANCE = 100; // Minimum color distance for new colors
let pieChart = null;
let colorDistribution = {};
let showPieChart = false; // Changed to false to hide by default
let updatePieChartThrottled = throttle(updatePieChart, 200);
let nodesToUpdate = [];
function throttle(func, limit) {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
}
}
function updatePieChart() {
const pieChartContainer = document.getElementById('pieChartContainer');
const colorDistributionElement = document.getElementById('colorDistribution');
if (!showPieChart) {
pieChartContainer.style.display = 'none';
colorDistributionElement.style.display = 'none';
return;
}
pieChartContainer.style.display = 'block';
colorDistributionElement.style.display = 'block';
pieChartContainer.style.zIndex = '10000';
pieChartContainer.style.width = '400px';
pieChartContainer.style.height = '450px';
pieChartContainer.style.backgroundColor = 'white';
pieChartContainer.style.padding = '10px';
pieChartContainer.style.borderRadius = '10px';
// Get current node colors
const currentColorDistribution = {};
nodes.forEach(node => {
const color = node.color.background;
currentColorDistribution[color] = (currentColorDistribution[color] || 0) + 1;
});
const colors = Object.keys(currentColorDistribution);
const data = colors.map(color => currentColorDistribution[color]);
if (!pieChart) {
const ctx = document.getElementById('pieChart').getContext('2d');
pieChart = new Chart(ctx, {
type: 'pie',
data: {
labels: colors,
datasets: [{
data: data,
backgroundColor: colors
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: true,
position: 'right',
labels: {
color: 'black'
}
},
tooltip: {
enabled: false
}
}
},
plugins: [{
afterDraw: function(chart) {
var ctx = chart.ctx;
ctx.save();
var centerX = (chart.chartArea.left + chart.chartArea.right) / 2;
var centerY = (chart.chartArea.top + chart.chartArea.bottom) / 2;
chart.data.datasets.forEach(function(dataset, datasetIndex) {
var meta = chart.getDatasetMeta(datasetIndex);
if (!meta.hidden) {
meta.data.forEach(function(element, index) {
var model = element.getProps(['startAngle', 'endAngle', 'circumference', 'outerRadius', 'x', 'y']);
var midAngle = model.startAngle + (model.endAngle - model.startAngle) / 2;
var x = centerX + Math.cos(midAngle) * (model.outerRadius * 0.7);
var y = centerY + Math.sin(midAngle) * (model.outerRadius * 0.7);
ctx.font = 'bold 12px Arial';
ctx.fillStyle = 'black';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
var percent = (dataset.data[index] / dataset.data.reduce((a, b) => a + b, 0) * 100).toFixed(1) + '%';
ctx.fillText(percent, x, y);
});
}
});
ctx.restore();
}
}]
});
} else {
pieChart.data.labels = colors;
pieChart.data.datasets[0].data = data;
pieChart.data.datasets[0].backgroundColor = colors;
pieChart.update();
}
// Update color distribution text
const totalNodes = nodes.length;
const distributionText = colors.map(color => {
const count = currentColorDistribution[color];
const percentage = ((count / totalNodes) * 100).toFixed(1);
return `${count}/${totalNodes} (${percentage}%) nodes are ${color}`;
}).join('<br>');
colorDistributionElement.innerHTML = distributionText;
colorDistributionElement.style.fontSize = '12px';
colorDistributionElement.style.marginTop = '10px';
colorDistributionElement.style.color = 'black';
}
// Update this to not show the pie chart when the page loads
window.addEventListener('load', () => {
updatePieChart(); // This will hide the pie chart initially
resizeAndCenterNetwork(); // Ensure the network is resized and centered when the page loads
});
document.addEventListener('keydown', (event) => {
if (event.key === 'p' || event.key === 'P') {
showPieChart = !showPieChart;
updatePieChart();
if (showPieChart) {
console.log('Pie chart is now visible');
} else {
console.log('Pie chart is now hidden');
}
}
});
const options = {
width: '100%',
height: '100%',
physics: {
enabled: true,
solver: 'forceAtlas2Based',
forceAtlas2Based: {
gravitationalConstant: -50,
centralGravity: 0.01,
springConstant: 0.08,
springLength: 80, // Reduced from 100 to 80 (20% closer)
damping: 0.4,
avoidOverlap: 0
},
stabilization: {
enabled: true,
iterations: 1000,
updateInterval: 100,
onlyDynamicEdges: false,
fit: true
}
},
nodes: {
shape: 'circle',
size: 28,
borderWidth: 0.2, // Reduced from 1 to 0.2 for a very thin border
color: {
border: '#ffffff', // Changed to white for a subtle border
background: '#e0e0e0'
}
},
edges: {
width: 1,
color: { color: '#ffffff', opacity: 0.5 }
}
};
// Set the background color to black
document.body.style.backgroundColor = '#000000';
// Create counter display
const counterDisplay = document.createElement('div');
counterDisplay.id = 'counterDisplay';
counterDisplay.style.position = 'fixed';
counterDisplay.style.top = '10px';
counterDisplay.style.right = '10px';
counterDisplay.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
counterDisplay.style.color = 'white';
counterDisplay.style.padding = '10px';
counterDisplay.style.borderRadius = '5px';
counterDisplay.style.fontFamily = 'Arial, sans-serif';
counterDisplay.style.fontSize = '14px';
counterDisplay.style.zIndex = '1000';
document.body.appendChild(counterDisplay);
function updateCounter() {
const colorCounts = {};
const totalNodes = nodes.length;
nodes.forEach(node => {
const color = node.color.background;
colorCounts[color] = (colorCounts[color] || 0) + 1;
});
let counterHTML = '';
Object.entries(colorCounts).forEach(([color, count]) => {
const percentage = ((count / totalNodes) * 100).toFixed(1);
counterHTML += `<div><span style="color:${color};">■</span> ${count} (${percentage}%)</div>`;
});
counterHTML += `<div>Total: ${totalNodes}</div>`;
counterDisplay.innerHTML = counterHTML;
}
const network = new vis.Network(container, data, options);
// Function to calculate the center of all nodes
function calculateNetworkCenter() {
const nodePositions = network.getPositions();
const nodeIds = Object.keys(nodePositions);
if (nodeIds.length === 0) return { x: 0, y: 0 };
const sum = nodeIds.reduce((acc, id) => {
acc.x += nodePositions[id].x;
acc.y += nodePositions[id].y;
return acc;
}, { x: 0, y: 0 });
return {
x: sum.x / nodeIds.length,
y: sum.y / nodeIds.length
};
}
// Ensure the network takes up the full browser window and center the view
function resizeAndCenterNetwork() {
container.style.width = '100vw';
container.style.height = '100vh';
const center = calculateNetworkCenter();
network.moveTo({
position: center,
scale: 1,
animation: {
duration: 1000,
easingFunction: 'easeInOutQuad'
}
});
}
window.addEventListener('resize', resizeAndCenterNetwork);
// Call this function once to center the network initially
// Removed the stabilizationIterationsDone event listener to avoid redundant calls to resizeAndCenterNetwork
socket.addEventListener('message', (event) => {
const logData = JSON.parse(event.data);
const { msg_hash, receivedTime, peerId, newNode } = logData;
if (newNode) {
nodes.add({
id: peerId,
title: `Node ${peerId}`,
color: {
background: DEFAULT_COLOR,
highlight: {
background: DEFAULT_COLOR
}
}
});
}
const colorHex = generateColorFromHash(msg_hash);
nodesToUpdate.push({
id: peerId,
color: {
background: colorHex,
highlight: {
background: colorHex
}
}
});
// Add or update edge
const edgeId = `${msg_hash}-${peerId}`;
if (!edges.get(edgeId)) {
edges.add({
id: edgeId,
from: msg_hash,
to: peerId,
arrows: 'to',
color: { color: '#ffffff', opacity: 0.5 }
});
}
});
setInterval(() => {
if (nodesToUpdate.length > 0) {
nodes.update(nodesToUpdate);
nodesToUpdate = [];
updatePieChartThrottled();
updateCounter();
}
}, 50);
// Initial counter update
updateCounter();
function generateColorFromHash(hash) {
const colorInt = parseInt(hash.slice(2), 16);
return '#' + (colorInt % 0xFFFFFF).toString(16).padStart(6, '0');
}
function hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
function rgbToHex(rgb) {
return "#" + ((1 << 24) + (rgb.r << 16) + (rgb.g << 8) + rgb.b).toString(16).slice(1);
}
function colorDistance(rgb1, rgb2) {
return Math.sqrt(
Math.pow(rgb1.r - rgb2.r, 2) +
Math.pow(rgb1.g - rgb2.g, 2) +
Math.pow(rgb1.b - rgb2.b, 2)
);
}