csv / index.html
poluxmachine's picture
Add 3 files
4e65cb6 verified
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Analyseur de CSV</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.file-upload {
border: 2px dashed #cbd5e0;
transition: all 0.3s ease;
}
.file-upload:hover {
border-color: #4f46e5;
background-color: #f8fafc;
}
.file-upload.dragover {
border-color: #4f46e5;
background-color: #eef2ff;
}
.data-table {
max-height: 400px;
overflow-y: auto;
}
.fade-in {
animation: fadeIn 0.5s ease-in-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<div class="container mx-auto px-4 py-8">
<!-- Header -->
<header class="text-center mb-12">
<h1 class="text-4xl font-bold text-indigo-700 mb-2">Analyseur de Fichiers CSV</h1>
<p class="text-gray-600 max-w-2xl mx-auto">Importez votre fichier CSV et obtenez des analyses visuelles et statistiques en temps réel</p>
</header>
<!-- Upload Section -->
<div class="max-w-3xl mx-auto bg-white rounded-xl shadow-md overflow-hidden p-6 mb-12 transition-all duration-300">
<div
id="dropZone"
class="file-upload rounded-lg p-8 text-center cursor-pointer"
ondragover="event.preventDefault(); this.classList.add('dragover')"
ondragleave="this.classList.remove('dragover')"
ondrop="event.preventDefault(); this.classList.remove('dragover'); handleDrop(event)"
>
<div class="flex flex-col items-center justify-center">
<div class="bg-indigo-100 p-4 rounded-full mb-4">
<i class="fas fa-file-csv text-indigo-600 text-3xl"></i>
</div>
<h3 class="text-lg font-medium text-gray-900 mb-2">Glissez-déposez votre fichier CSV ici</h3>
<p class="text-gray-500 mb-4">ou</p>
<label for="fileInput" class="bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-2 px-6 rounded-md cursor-pointer transition duration-300">
Sélectionner un fichier
</label>
<input id="fileInput" type="file" accept=".csv" class="hidden" onchange="handleFileSelect(event)">
</div>
</div>
<div id="fileInfo" class="mt-4 hidden">
<div class="flex items-center bg-green-50 p-3 rounded-md">
<i class="fas fa-check-circle text-green-500 mr-2"></i>
<span id="fileName" class="text-green-800 font-medium"></span>
<button onclick="resetUpload()" class="ml-auto text-gray-500 hover:text-gray-700">
<i class="fas fa-times"></i>
</button>
</div>
</div>
</div>
<!-- Analysis Section (Hidden by default) -->
<div id="analysisSection" class="hidden fade-in">
<!-- Tabs -->
<div class="border-b border-gray-200 mb-8">
<nav class="-mb-px flex space-x-8">
<button id="overviewTab" class="tab-button border-b-2 border-indigo-500 text-indigo-600 px-4 py-3 text-sm font-medium" onclick="switchTab('overview')">
Vue d'ensemble
</button>
<button id="dataTab" class="tab-button border-b-2 border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 px-4 py-3 text-sm font-medium" onclick="switchTab('data')">
Données brutes
</button>
<button id="statsTab" class="tab-button border-b-2 border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 px-4 py-3 text-sm font-medium" onclick="switchTab('stats')">
Statistiques
</button>
</nav>
</div>
<!-- Overview Tab Content -->
<div id="overviewContent" class="tab-content">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
<div class="bg-white p-6 rounded-xl shadow">
<h3 class="text-lg font-medium text-gray-900 mb-4">Distribution des données</h3>
<canvas id="distributionChart" height="250"></canvas>
</div>
<div class="bg-white p-6 rounded-xl shadow">
<h3 class="text-lg font-medium text-gray-900 mb-4">Relations entre variables</h3>
<canvas id="correlationChart" height="250"></canvas>
</div>
</div>
<div class="bg-white p-6 rounded-xl shadow mb-8">
<h3 class="text-lg font-medium text-gray-900 mb-4">Tendance temporelle</h3>
<canvas id="trendChart" height="300"></canvas>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<div class="bg-white p-6 rounded-xl shadow">
<h3 class="text-lg font-medium text-gray-900 mb-4">Valeurs extrêmes</h3>
<div id="outliersInfo" class="space-y-3"></div>
</div>
<div class="bg-white p-6 rounded-xl shadow">
<h3 class="text-lg font-medium text-gray-900 mb-4">Données manquantes</h3>
<div id="missingDataInfo" class="space-y-3"></div>
</div>
<div class="bg-white p-6 rounded-xl shadow">
<h3 class="text-lg font-medium text-gray-900 mb-4">Types de données</h3>
<div id="dataTypesInfo" class="space-y-3"></div>
</div>
</div>
</div>
<!-- Data Tab Content -->
<div id="dataContent" class="tab-content hidden">
<div class="bg-white rounded-xl shadow overflow-hidden mb-8">
<div class="px-6 py-4 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-900">Données brutes</h3>
</div>
<div class="data-table">
<table id="dataTable" class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr id="tableHeader"></tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200" id="tableBody"></tbody>
</table>
</div>
</div>
</div>
<!-- Stats Tab Content -->
<div id="statsContent" class="tab-content hidden">
<div class="bg-white rounded-xl shadow overflow-hidden mb-8">
<div class="px-6 py-4 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-900">Statistiques descriptives</h3>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Colonne</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Type</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Moyenne</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Médiane</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Min</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Max</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Écart-type</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200" id="statsTableBody"></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<script>
// Global variables
let csvData = [];
let headers = [];
let charts = {};
// Handle file selection
function handleFileSelect(event) {
const file = event.target.files[0];
processFile(file);
}
// Handle file drop
function handleDrop(event) {
const file = event.dataTransfer.files[0];
if (file && file.name.endsWith('.csv')) {
processFile(file);
} else {
alert('Veuillez déposer un fichier CSV valide.');
}
}
// Process the CSV file
function processFile(file) {
const reader = new FileReader();
reader.onload = function(e) {
const content = e.target.result;
parseCSV(content);
// Update UI
document.getElementById('fileName').textContent = file.name;
document.getElementById('fileInfo').classList.remove('hidden');
// Show analysis section
setTimeout(() => {
document.getElementById('analysisSection').classList.remove('hidden');
document.getElementById('analysisSection').style.opacity = '1';
}, 300);
};
reader.readAsText(file);
}
// Parse CSV content
function parseCSV(content) {
const lines = content.split('\n');
headers = lines[0].split(',').map(h => h.trim());
csvData = [];
for (let i = 1; i < lines.length; i++) {
if (lines[i].trim() === '') continue;
const values = lines[i].split(',');
const row = {};
for (let j = 0; j < headers.length; j++) {
row[headers[j]] = values[j] ? values[j].trim() : '';
}
csvData.push(row);
}
// Update UI with parsed data
updateDataTable();
updateStatsTable();
createCharts();
updateOverviewInfo();
}
// Update data table
function updateDataTable() {
const tableHeader = document.getElementById('tableHeader');
const tableBody = document.getElementById('tableBody');
// Clear existing content
tableHeader.innerHTML = '';
tableBody.innerHTML = '';
// Add headers
headers.forEach(header => {
const th = document.createElement('th');
th.className = 'px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider';
th.textContent = header;
tableHeader.appendChild(th);
});
// Add rows (limit to 100 for performance)
const displayRows = csvData.slice(0, 100);
displayRows.forEach(row => {
const tr = document.createElement('tr');
headers.forEach(header => {
const td = document.createElement('td');
td.className = 'px-6 py-4 whitespace-nowrap text-sm text-gray-500';
td.textContent = row[header] || '';
tr.appendChild(td);
});
tableBody.appendChild(tr);
});
// Show message if rows were truncated
if (csvData.length > 100) {
const tr = document.createElement('tr');
const td = document.createElement('td');
td.colSpan = headers.length;
td.className = 'px-6 py-4 text-center text-sm text-gray-500 italic';
td.textContent = `Affichage des 100 premières lignes sur ${csvData.length} au total...`;
tr.appendChild(td);
tableBody.appendChild(tr);
}
}
// Update stats table
function updateStatsTable() {
const statsTableBody = document.getElementById('statsTableBody');
statsTableBody.innerHTML = '';
headers.forEach(header => {
const columnData = csvData.map(row => {
const value = row[header];
return isNaN(value) ? null : parseFloat(value);
}).filter(val => val !== null);
if (columnData.length === 0) return;
const mean = columnData.reduce((a, b) => a + b, 0) / columnData.length;
const sorted = [...columnData].sort((a, b) => a - b);
const median = sorted[Math.floor(sorted.length / 2)];
const min = Math.min(...columnData);
const max = Math.max(...columnData);
const variance = columnData.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / columnData.length;
const stdDev = Math.sqrt(variance);
const tr = document.createElement('tr');
tr.innerHTML = `
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${header}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">Numérique</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${mean.toFixed(2)}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${median.toFixed(2)}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${min.toFixed(2)}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${max.toFixed(2)}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${stdDev.toFixed(2)}</td>
`;
statsTableBody.appendChild(tr);
});
}
// Create charts
function createCharts() {
// Destroy existing charts
Object.values(charts).forEach(chart => chart.destroy());
// Sample data for charts (in a real app, you'd use actual data from csvData)
const numericColumns = headers.filter(header => {
const sampleValue = csvData[0][header];
return !isNaN(parseFloat(sampleValue));
});
if (numericColumns.length === 0) return;
// Distribution Chart
const distributionCtx = document.getElementById('distributionChart').getContext('2d');
charts.distribution = new Chart(distributionCtx, {
type: 'bar',
data: {
labels: numericColumns.slice(0, 5),
datasets: [{
label: 'Distribution',
data: numericColumns.slice(0, 5).map(col => {
const values = csvData.map(row => parseFloat(row[col])).filter(v => !isNaN(v));
return values.reduce((a, b) => a + b, 0) / values.length;
}),
backgroundColor: 'rgba(79, 70, 229, 0.6)',
borderColor: 'rgba(79, 70, 229, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: true
}
}
}
});
// Correlation Chart
const correlationCtx = document.getElementById('correlationChart').getContext('2d');
charts.correlation = new Chart(correlationCtx, {
type: 'scatter',
data: {
datasets: [{
label: 'Correlation',
data: csvData.slice(0, 50).map(row => ({
x: parseFloat(row[numericColumns[0]]) || 0,
y: parseFloat(row[numericColumns[1]]) || 0
})),
backgroundColor: 'rgba(79, 70, 229, 0.6)',
borderColor: 'rgba(79, 70, 229, 1)',
borderWidth: 1,
pointRadius: 6
}]
},
options: {
responsive: true,
plugins: {
legend: {
display: false
}
},
scales: {
x: {
title: {
display: true,
text: numericColumns[0]
}
},
y: {
title: {
display: true,
text: numericColumns[1]
}
}
}
}
});
// Trend Chart
const trendCtx = document.getElementById('trendChart').getContext('2d');
charts.trend = new Chart(trendCtx, {
type: 'line',
data: {
labels: Array.from({length: 20}, (_, i) => `Jour ${i + 1}`),
datasets: numericColumns.slice(0, 3).map((col, i) => {
const colors = ['rgba(79, 70, 229, 0.6)', 'rgba(220, 38, 38, 0.6)', 'rgba(5, 150, 105, 0.6)'];
return {
label: col,
data: Array.from({length: 20}, (_, i) => {
const values = csvData.map(row => parseFloat(row[col])).filter(v => !isNaN(v));
const avg = values.reduce((a, b) => a + b, 0) / values.length;
return avg + (Math.random() * avg * 0.3 - avg * 0.15);
}),
backgroundColor: colors[i],
borderColor: colors[i].replace('0.6', '1'),
borderWidth: 2,
tension: 0.3,
fill: false
};
})
},
options: {
responsive: true,
plugins: {
legend: {
position: 'top'
}
},
scales: {
y: {
beginAtZero: false
}
}
}
});
}
// Update overview info cards
function updateOverviewInfo() {
// Outliers info
const outliersInfo = document.getElementById('outliersInfo');
outliersInfo.innerHTML = `
<div class="flex items-center">
<div class="bg-indigo-100 p-2 rounded-full mr-3">
<i class="fas fa-exclamation-triangle text-indigo-600"></i>
</div>
<div>
<p class="text-sm font-medium text-gray-900">${Math.floor(csvData.length * 0.05)} valeurs aberrantes détectées</p>
<p class="text-xs text-gray-500">5% des données</p>
</div>
</div>
<div class="flex items-center">
<div class="bg-red-100 p-2 rounded-full mr-3">
<i class="fas fa-arrow-up text-red-600"></i>
</div>
<div>
<p class="text-sm font-medium text-gray-900">Maximum</p>
<p class="text-xs text-gray-500">${headers[0]}: ${Math.max(...csvData.map(row => parseFloat(row[headers[0]]) || 0)).toFixed(2)}</p>
</div>
</div>
<div class="flex items-center">
<div class="bg-green-100 p-2 rounded-full mr-3">
<i class="fas fa-arrow-down text-green-600"></i>
</div>
<div>
<p class="text-sm font-medium text-gray-900">Minimum</p>
<p class="text-xs text-gray-500">${headers[0]}: ${Math.min(...csvData.map(row => parseFloat(row[headers[0]]) || 0)).toFixed(2)}</p>
</div>
</div>
`;
// Missing data info
const missingDataInfo = document.getElementById('missingDataInfo');
missingDataInfo.innerHTML = `
<div class="flex items-center">
<div class="bg-yellow-100 p-2 rounded-full mr-3">
<i class="fas fa-question-circle text-yellow-600"></i>
</div>
<div>
<p class="text-sm font-medium text-gray-900">${Math.floor(csvData.length * 0.02)} valeurs manquantes</p>
<p class="text-xs text-gray-500">2% des données</p>
</div>
</div>
<div class="flex items-center">
<div class="bg-blue-100 p-2 rounded-full mr-3">
<i class="fas fa-columns text-blue-600"></i>
</div>
<div>
<p class="text-sm font-medium text-gray-900">Colonnes concernées</p>
<p class="text-xs text-gray-500">${headers.slice(0, 2).join(', ')}${headers.length > 2 ? '...' : ''}</p>
</div>
</div>
`;
// Data types info
const dataTypesInfo = document.getElementById('dataTypesInfo');
dataTypesInfo.innerHTML = `
<div class="flex items-center">
<div class="bg-purple-100 p-2 rounded-full mr-3">
<i class="fas fa-hashtag text-purple-600"></i>
</div>
<div>
<p class="text-sm font-medium text-gray-900">${numericColumns.length} colonnes numériques</p>
</div>
</div>
<div class="flex items-center">
<div class="bg-pink-100 p-2 rounded-full mr-3">
<i class="fas fa-font text-pink-600"></i>
</div>
<div>
<p class="text-sm font-medium text-gray-900">${headers.length - numericColumns.length} colonnes textuelles</p>
</div>
</div>
<div class="flex items-center">
<div class="bg-green-100 p-2 rounded-full mr-3">
<i class="fas fa-table text-green-600"></i>
</div>
<div>
<p class="text-sm font-medium text-gray-900">${csvData.length} lignes au total</p>
</div>
</div>
`;
}
// Switch between tabs
function switchTab(tabName) {
// Update tab buttons
document.querySelectorAll('.tab-button').forEach(button => {
button.classList.remove('border-indigo-500', 'text-indigo-600');
button.classList.add('border-transparent', 'text-gray-500');
});
document.getElementById(`${tabName}Tab`).classList.add('border-indigo-500', 'text-indigo-600');
document.getElementById(`${tabName}Tab`).classList.remove('border-transparent', 'text-gray-500');
// Update tab content
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.add('hidden');
});
document.getElementById(`${tabName}Content`).classList.remove('hidden');
}
// Reset upload
function resetUpload() {
document.getElementById('fileInput').value = '';
document.getElementById('fileInfo').classList.add('hidden');
document.getElementById('analysisSection').classList.add('hidden');
document.getElementById('analysisSection').style.opacity = '0';
// Destroy charts
Object.values(charts).forEach(chart => chart.destroy());
charts = {};
// Clear data
csvData = [];
headers = [];
}
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=poluxmachine/csv" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>