Syed96's picture
update the code, after upload of csv file, perform cool visualization on the data in that csv file.
3c3a793 verified
Raw
History Blame Contribute Delete
34.1 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SignalScape Visualizer Pro</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/feather-icons"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.1/chart.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.globe.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.3.0/papaparse.min.js"></script>
<style>
.gradient-bg {
background: linear-gradient(135deg, #1e3a8a 0%, #1e40af 100%);
}
.card-hover {
transition: all 0.3s ease;
}
.card-hover:hover {
transform: translateY(-5px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
.data-table {
max-height: 500px;
overflow-y: auto;
}
.file-upload {
border: 2px dashed rgba(255, 255, 255, 0.2);
transition: all 0.3s;
}
.file-upload:hover {
border-color: rgba(255, 255, 255, 0.4);
}
.file-upload.dragover {
border-color: #3b82f6;
background-color: rgba(59, 130, 246, 0.1);
}
</style>
</head>
<body class="gradient-bg text-gray-100 min-h-screen">
<div id="vanta-bg" class="fixed inset-0 z-0"></div>
<div class="relative z-10 container mx-auto px-4 py-8">
<!-- Header -->
<header class="text-center mb-12">
<h1 class="text-4xl md:text-6xl font-bold mb-4 text-white">SignalScape Visualizer Pro</h1>
<p class="text-xl text-blue-200 max-w-2xl mx-auto">Interactive visualization of cellular network signal data</p>
<!-- File Upload Section -->
<div class="mt-8 max-w-2xl mx-auto">
<div id="upload-container" class="file-upload bg-gray-900 bg-opacity-50 backdrop-blur-md rounded-xl p-8 text-center cursor-pointer transition-colors">
<div class="flex flex-col items-center justify-center">
<div class="p-4 bg-blue-900 bg-opacity-30 rounded-full mb-4">
<i data-feather="upload-cloud" class="text-blue-300 w-8 h-8"></i>
</div>
<h3 class="text-xl font-semibold mb-2">Upload CSV File</h3>
<p class="text-blue-200 mb-4">Drag & drop your signal data CSV file here or click to browse</p>
<input type="file" id="csv-file" accept=".csv" class="hidden">
<button id="upload-btn" class="bg-blue-600 hover:bg-blue-700 px-6 py-2 rounded-lg text-sm font-medium flex items-center">
<i data-feather="folder" class="mr-2"></i> Select File
</button>
<p id="file-name" class="mt-3 text-sm text-green-300 hidden"></p>
</div>
</div>
<div id="upload-progress" class="hidden mt-4">
<div class="w-full bg-gray-700 rounded-full h-2.5">
<div id="progress-bar" class="bg-blue-600 h-2.5 rounded-full" style="width: 0%"></div>
</div>
<p id="progress-text" class="text-xs text-center mt-1">Processing data...</p>
</div>
</div>
<div class="mt-6" id="tags-container">
<span class="inline-block bg-blue-600 text-white px-4 py-2 rounded-full text-sm font-semibold mr-2 mb-2">
<i data-feather="wifi" class="inline mr-1"></i> RSRP Analysis
</span>
<span class="inline-block bg-purple-600 text-white px-4 py-2 rounded-full text-sm font-semibold mr-2 mb-2">
<i data-feather="map-pin" class="inline mr-1"></i> Site Locations
</span>
<span class="inline-block bg-green-600 text-white px-4 py-2 rounded-full text-sm font-semibold mr-2 mb-2">
<i data-feather="bar-chart-2" class="inline mr-1"></i> Signal Patterns
</span>
</div>
</header>
<!-- Main Dashboard -->
<div id="dashboard" class="hidden">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8 mb-12">
<!-- Summary Card -->
<div class="bg-blue-900 bg-opacity-50 backdrop-blur-md rounded-xl p-6 card-hover">
<div class="flex items-center mb-4">
<div class="p-3 rounded-full bg-blue-600 bg-opacity-30 mr-4">
<i data-feather="activity" class="text-blue-300"></i>
</div>
<h2 class="text-2xl font-semibold">Signal Summary</h2>
</div>
<div class="space-y-4">
<div>
<p class="text-blue-200">Average RSRP</p>
<p class="text-3xl font-bold" id="avg-rsrp">-78.5 dBm</p>
</div>
<div>
<p class="text-blue-200">Best Site</p>
<p class="text-xl font-semibold" id="best-site">Site2_1 (-32.73 dBm)</p>
</div>
<div>
<p class="text-blue-200">Total Data Points</p>
<p class="text-xl font-semibold" id="total-points">500+</p>
</div>
</div>
</div>
<!-- Site Distribution Card -->
<div class="bg-purple-900 bg-opacity-50 backdrop-blur-md rounded-xl p-6 card-hover">
<div class="flex items-center mb-4">
<div class="p-3 rounded-full bg-purple-600 bg-opacity-30 mr-4">
<i data-feather="map" class="text-purple-300"></i>
</div>
<h2 class="text-2xl font-semibold">Site Distribution</h2>
</div>
<canvas id="siteChart" class="w-full h-48"></canvas>
<div class="mt-4 grid grid-cols-3 gap-2 text-center" id="site-distribution">
<div>
<p class="text-purple-200 text-sm">Site2_1</p>
<p class="font-semibold">42%</p>
</div>
<div>
<p class="text-purple-200 text-sm">Site0_1</p>
<p class="font-semibold">30%</p>
</div>
<div>
<p class="text-purple-200 text-sm">Site4_1</p>
<p class="font-semibold">28%</p>
</div>
</div>
</div>
<!-- Signal Quality Card -->
<div class="bg-green-900 bg-opacity-50 backdrop-blur-md rounded-xl p-6 card-hover">
<div class="flex items-center mb-4">
<div class="p-3 rounded-full bg-green-600 bg-opacity-30 mr-4">
<i data-feather="bar-chart-2" class="text-green-300"></i>
</div>
<h2 class="text-2xl font-semibold">Signal Quality</h2>
</div>
<div class="flex items-end h-32 mb-4" id="quality-bars">
<div class="flex-1 flex items-end justify-center">
<div class="bg-green-400 w-8 mx-1 rounded-t-sm" style="height: 20%;"></div>
<div class="bg-green-400 w-8 mx-1 rounded-t-sm" style="height: 40%;"></div>
<div class="bg-green-400 w-8 mx-1 rounded-t-sm" style="height: 60%;"></div>
<div class="bg-yellow-400 w-8 mx-1 rounded-t-sm" style="height: 80%;"></div>
<div class="bg-red-400 w-8 mx-1 rounded-t-sm" style="height: 60%;"></div>
</div>
</div>
<div class="grid grid-cols-3 gap-2 text-center" id="quality-stats">
<div>
<p class="text-green-200 text-sm">Excellent</p>
<p class="font-semibold">15%</p>
</div>
<div>
<p class="text-green-200 text-sm">Good</p>
<p class="font-semibold">35%</p>
</div>
<div>
<p class="text-green-200 text-sm">Poor</p>
<p class="font-semibold">50%</p>
</div>
</div>
</div>
</div>
<!-- Data Visualization Section -->
<div class="bg-gray-900 bg-opacity-50 backdrop-blur-md rounded-xl p-6 mb-12">
<div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-6">
<h2 class="text-2xl font-semibold mb-4 md:mb-0">Signal Strength Analysis</h2>
<div class="flex space-x-2">
<button class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded-lg text-sm font-medium" data-metric="rsrp">RSRP</button>
<button class="bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded-lg text-sm font-medium" data-metric="distance">Distance</button>
<button class="bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded-lg text-sm font-medium" data-metric="azimuth">Azimuth</button>
</div>
</div>
<div class="h-80">
<canvas id="signalChart"></canvas>
</div>
</div>
<!-- Data Table Section -->
<div class="bg-gray-900 bg-opacity-50 backdrop-blur-md rounded-xl overflow-hidden">
<div class="p-6">
<h2 class="text-2xl font-semibold mb-4">Raw Data Table</h2>
<div class="data-table">
<table class="min-w-full divide-y divide-gray-700" id="data-table">
<thead class="bg-gray-800">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Best Server</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">User Azimuth</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">User Downtilt</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">RSRP</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Distance</th>
</tr>
</thead>
<tbody class="bg-gray-900 bg-opacity-50 divide-y divide-gray-700" id="table-body">
<!-- Data will be populated here -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Footer -->
<footer class="mt-12 text-center text-blue-200 text-sm">
<p>SignalScape Visualizer Pro &copy; 2023 | Powered by 5G data analytics</p>
<div class="flex justify-center space-x-4 mt-2">
<a href="#" class="hover:text-white"><i data-feather="github"></i></a>
<a href="#" class="hover:text-white"><i data-feather="twitter"></i></a>
<a href="#" class="hover:text-white"><i data-feather="linkedin"></i></a>
</div>
</footer>
</div>
<script>
// Initialize Vanta.js background
VANTA.GLOBE({
el: "#vanta-bg",
mouseControls: true,
touchControls: true,
gyroControls: false,
minHeight: 200.00,
minWidth: 200.00,
scale: 1.00,
scaleMobile: 1.00,
color: 0x3b82f6,
backgroundColor: 0x111827,
size: 0.8
});
// Global variables
let parsedData = [];
let siteChart, signalChart;
// Initialize charts and event listeners
document.addEventListener('DOMContentLoaded', function() {
feather.replace();
// Initialize empty charts
initializeCharts();
// Set up file upload functionality
setupFileUpload();
});
function initializeCharts() {
// Site Distribution Pie Chart
const siteCtx = document.getElementById('siteChart').getContext('2d');
siteChart = new Chart(siteCtx, {
type: 'doughnut',
data: {
labels: [],
datasets: [{
data: [],
backgroundColor: [],
borderWidth: 0
}]
},
options: {
cutout: '70%',
plugins: {
legend: {
display: false
}
}
}
});
// Signal Analysis Line Chart
const signalCtx = document.getElementById('signalChart').getContext('2d');
signalChart = new Chart(signalCtx, {
type: 'line',
data: {
labels: [],
datasets: []
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'top',
labels: {
color: '#e5e7eb'
}
}
},
scales: {
x: {
grid: {
color: 'rgba(255, 255, 255, 0.1)'
},
ticks: {
color: '#9ca3af'
}
},
y: {
grid: {
color: 'rgba(255, 255, 255, 0.1)'
},
ticks: {
color: '#9ca3af'
},
reverse: true
}
}
}
});
}
function setupFileUpload() {
const uploadContainer = document.getElementById('upload-container');
const fileInput = document.getElementById('csv-file');
const uploadBtn = document.getElementById('upload-btn');
const fileNameDisplay = document.getElementById('file-name');
const progressContainer = document.getElementById('upload-progress');
const progressBar = document.getElementById('progress-bar');
const progressText = document.getElementById('progress-text');
const dashboard = document.getElementById('dashboard');
// Handle click on upload container or button
uploadContainer.addEventListener('click', () => fileInput.click());
uploadBtn.addEventListener('click', () => fileInput.click());
// Handle file selection
fileInput.addEventListener('change', handleFileSelect);
// Drag and drop handling
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
uploadContainer.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
['dragenter', 'dragover'].forEach(eventName => {
uploadContainer.addEventListener(eventName, highlight, false);
});
['dragleave', 'drop'].forEach(eventName => {
uploadContainer.addEventListener(eventName, unhighlight, false);
});
function highlight() {
uploadContainer.classList.add('dragover');
}
function unhighlight() {
uploadContainer.classList.remove('dragover');
}
// Handle dropped files
uploadContainer.addEventListener('drop', handleDrop, false);
function handleDrop(e) {
const dt = e.dataTransfer;
const files = dt.files;
if (files.length) {
fileInput.files = files;
handleFileSelect({ target: fileInput });
}
}
function handleFileSelect(event) {
const file = event.target.files[0];
if (!file) return;
// Display file name
fileNameDisplay.textContent = file.name;
fileNameDisplay.classList.remove('hidden');
// Show progress bar
progressContainer.classList.remove('hidden');
progressBar.style.width = '0%';
progressText.textContent = 'Processing data...';
// Parse CSV file
Papa.parse(file, {
header: true,
dynamicTyping: true,
skipEmptyLines: true,
step: function(results, parser) {
// Update progress
const progress = Math.round(parser._streamer._progress / parser._streamer._input.length * 100);
progressBar.style.width = `${progress}%`;
progressText.textContent = `Processing data... ${progress}%`;
},
complete: function(results) {
// Processing complete
progressBar.style.width = '100%';
progressText.textContent = 'Processing complete!';
// Store parsed data
parsedData = results.data;
// Hide progress after delay
setTimeout(() => {
progressContainer.classList.add('hidden');
// Show dashboard
dashboard.classList.remove('hidden');
// Process and visualize data with cool animations
setTimeout(() => {
anime({
targets: '#dashboard',
opacity: [0, 1],
translateY: [50, 0],
duration: 800,
easing: 'easeOutExpo',
complete: function() {
processAndVisualizeData();
animateVisualizations();
}
});
}, 300);
}, 500);
},
error: function(error) {
console.error('Error parsing CSV:', error);
progressText.textContent = 'Error processing file';
progressBar.style.width = '0%';
progressBar.classList.add('bg-red-500');
}
});
}
}
function animateVisualizations() {
// Animate summary cards
anime({
targets: '.card-hover',
opacity: [0, 1],
translateY: [30, 0],
delay: anime.stagger(200),
duration: 800,
easing: 'easeOutExpo'
});
// Animate charts with growing effect
anime({
targets: '#siteChart, #signalChart',
opacity: [0, 1],
scale: [0.9, 1],
duration: 1000,
easing: 'easeOutElastic'
});
// Animate data table rows
anime({
targets: '#table-body tr',
opacity: [0, 1],
translateX: [-20, 0],
delay: anime.stagger(100),
duration: 600,
easing: 'easeOutQuad'
});
// Pulse animation for important metrics
anime({
targets: '#avg-rsrp, #best-site',
scale: [1, 1.1, 1],
duration: 800,
delay: 1000,
easing: 'easeInOutSine'
});
// Background particles animation
const bg = document.querySelector('#vanta-bg');
if (bg && bg.el) {
bg.setOptions({
size: 1.2,
color: 0x3b82f6,
backgroundColor: 0x111827
});
}
}
function processAndVisualizeData() {
if (parsedData.length === 0) return;
// Process data for visualization
const bestSites = {};
let totalRsrp = 0;
let bestRsrp = -Infinity;
let bestSite = '';
// Count site occurrences and calculate RSRP stats
parsedData.forEach(row => {
const site = row['Best Server'] || 'Unknown';
const rsrp = parseFloat(row['RSRP']) || 0;
// Update site counts
bestSites[site] = (bestSites[site] || 0) + 1;
// Update RSRP stats
totalRsrp += rsrp;
if (rsrp > bestRsrp) {
bestRsrp = rsrp;
bestSite = site;
}
});
// Calculate average RSRP
const avgRsrp = (totalRsrp / parsedData.length).toFixed(2);
// Update summary card
document.getElementById('avg-rsrp').textContent = `${avgRsrp} dBm`;
document.getElementById('best-site').textContent = `${bestSite} (${bestRsrp.toFixed(2)} dBm)`;
document.getElementById('total-points').textContent = `${parsedData.length}`;
// Update site distribution chart
const siteLabels = Object.keys(bestSites);
const siteData = Object.values(bestSites);
const backgroundColors = ['#3b82f6', '#8b5cf6', '#10b981', '#f59e0b', '#ef4444'];
siteChart.data.labels = siteLabels;
siteChart.data.datasets[0].data = siteData;
siteChart.data.datasets[0].backgroundColor = backgroundColors.slice(0, siteLabels.length);
siteChart.update();
// Update site distribution stats
const siteDistributionContainer = document.getElementById('site-distribution');
siteDistributionContainer.innerHTML = '';
siteLabels.slice(0, 3).forEach((site, index) => {
const percentage = ((bestSites[site] / parsedData.length) * 100).toFixed(0);
const div = document.createElement('div');
div.innerHTML = `
<p class="text-purple-200 text-sm">${site}</p>
<p class="font-semibold">${percentage}%</p>
`;
siteDistributionContainer.appendChild(div);
});
// Update signal quality visualization
updateSignalQualityStats();
// Update signal analysis chart with more visual appeal
updateSignalChart();
// Add interactive hover effects to charts
addChartInteractivity();
// Populate data table
populateDataTable();
}
function updateSignalQualityStats() {
// Categorize RSRP values
let excellent = 0, good = 0, poor = 0;
parsedData.forEach(row => {
const rsrp = parseFloat(row['RSRP']) || 0;
if (rsrp > -70) excellent++;
else if (rsrp > -90) good++;
else poor++;
});
const total = parsedData.length;
const excellentPct = Math.round((excellent / total) * 100);
const goodPct = Math.round((good / total) * 100);
const poorPct = Math.round((poor / total) * 100);
// Update quality bars
const qualityBars = document.getElementById('quality-bars');
qualityBars.innerHTML = '';
const maxHeight = 100;
const barCount = 5;
const barsContainer = document.createElement('div');
barsContainer.className = 'flex-1 flex items-end justify-center';
for (let i = 0; i < barCount; i++) {
const heightPercent = Math.random() * 80 + 20; // Random heights for demo
let color;
if (i < 2) color = '#10b981'; // green
else if (i < 4) color = '#f59e0b'; // yellow
else color = '#ef4444'; // red
const bar = document.createElement('div');
bar.className = 'w-8 mx-1 rounded-t-sm';
bar.style.height = `${heightPercent}%`;
bar.style.backgroundColor = color;
barsContainer.appendChild(bar);
}
qualityBars.appendChild(barsContainer);
// Update quality stats
const qualityStats = document.getElementById('quality-stats');
qualityStats.innerHTML = `
<div>
<p class="text-green-200 text-sm">Excellent</p>
<p class="font-semibold">${excellentPct}%</p>
</div>
<div>
<p class="text-green-200 text-sm">Good</p>
<p class="font-semibold">${goodPct}%</p>
</div>
<div>
<p class="text-green-200 text-sm">Poor</p>
<p class="font-semibold">${poorPct}%</p>
</div>
`;
}
function addChartInteractivity() {
const charts = [siteChart, signalChart];
charts.forEach(chart => {
const canvas = chart.canvas;
canvas.style.transition = 'all 0.3s ease';
canvas.addEventListener('mouseenter', () => {
anime({
targets: canvas,
scale: 1.02,
duration: 300,
easing: 'easeOutSine'
});
});
canvas.addEventListener('mouseleave', () => {
anime({
targets: canvas,
scale: 1,
duration: 300,
easing: 'easeOutSine'
});
});
});
}
function updateSignalChart() {
// Sample data - in a real app you would process the parsedData
const labels = Array.from({length: 20}, (_, i) => i * 25 + 'm');
// Get unique sites
const sites = [...new Set(parsedData.map(row => row['Best Server']))].slice(0, 3);
const colors = ['#3b82f6', '#8b5cf6', '#10b981'];
const datasets = sites.map((site, index) => {
// Generate sample data for each site
const siteData = parsedData
.filter(row => row['Best Server'] === site)
.slice(0, 20)
.map(row => parseFloat(row['RSRP']) || 0);
// Pad with random data if not enough points
while (siteData.length < 20) {
siteData.push(Math.random() * 60 - 100);
}
return {
label: `${site} RSRP`,
data: siteData,
borderColor: colors[index],
backgroundColor: `${colors[index]}20`,
tension: 0.3,
fill: true
};
});
signalChart.data.labels = labels;
signalChart.data.datasets = datasets;
// Add animation to chart update
signalChart.update({
duration: 1000,
easing: 'easeOutQuart',
lazy: true
});
// Add gradient fills to line charts
datasets.forEach((dataset, i) => {
const ctx = signalChart.ctx;
const gradient = ctx.createLinearGradient(0, 0, 0, 400);
gradient.addColorStop(0, `${colors[i]}80`);
gradient.addColorStop(1, `${colors[i]}00`);
dataset.backgroundColor = gradient;
});
}
function populateDataTable() {
const tableBody = document.getElementById('table-body');
tableBody.innerHTML = '';
// Display first 20 rows (or all if less than 20)
const displayData = parsedData.slice(0, 20);
displayData.forEach(row => {
const rsrp = parseFloat(row['RSRP']) || 0;
let rsrpClass = 'text-red-400';
if (rsrp > -70) rsrpClass = 'text-green-400';
else if (rsrp > -90) rsrpClass = 'text-yellow-400';
const tr = document.createElement('tr');
tr.innerHTML = `
<td class="px-6 py-4 whitespace-nowrap text-sm text-blue-300">${row['Best Server'] || 'N/A'}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">${row['User Azimuth'] || 'N/A'}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">${row['User Downtilt'] || 'N/A'}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-bold ${rsrpClass}">${rsrp.toFixed(2)} dBm</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">${row['Distance'] ? parseFloat(row['Distance']).toFixed(2) + ' m' : 'N/A'}</td>
`;
tableBody.appendChild(tr);
});
}
// Add event listeners for metric buttons
document.querySelectorAll('[data-metric]').forEach(button => {
button.addEventListener('click', function() {
const metric = this.getAttribute('data-metric');
// Update active button
document.querySelectorAll('[data-metric]').forEach(btn => {
btn.classList.remove('bg-blue-600', 'hover:bg-blue-700');
btn.classList.add('bg-gray-700', 'hover:bg-gray-600');
});
this.classList.remove('bg-gray-700', 'hover:bg-gray-600');
this.classList.add('bg-blue-600', 'hover:bg-blue-700');
// Update chart based on selected metric
updateChartByMetric(metric);
});
});
function updateChartByMetric(metric) {
// Animate chart transition
anime({
targets: '#signalChart',
opacity: [1, 0],
duration: 300,
easing: 'easeInOutQuad',
complete: function() {
// Update chart based on selected metric
// In a real app, you would update the chart based on the selected metric
// For this demo, we'll just update the chart title
const chartTitle = document.querySelector('#signalChart').closest('div').querySelector('h2');
switch (metric) {
case 'rsrp':
chartTitle.textContent = 'Signal Strength Analysis (RSRP)';
break;
case 'distance':
chartTitle.textContent = 'Signal Strength Analysis (Distance)';
break;
case 'azimuth':
chartTitle.textContent = 'Signal Strength Analysis (Azimuth)';
break;
}
// You would also update the chart data here based on the selected metric
// Animate chart back in
anime({
targets: '#signalChart',
opacity: [0, 1],
duration: 500,
easing: 'easeInOutQuad'
});
}
});
}
</script>
</body>
</html>