deepsite / index.html
anzuo's picture
Add 3 files
79eb658 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Large CSV Analyzer</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/papaparse@5.3.0/papaparse.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.dropzone {
border: 2px dashed #ccc;
transition: all 0.3s ease;
}
.dropzone.active {
border-color: #4f46e5;
background-color: #f0f7ff;
}
.progress-bar {
transition: width 0.3s ease;
}
.column-selector:hover {
background-color: #f3f4f6;
}
.column-type-selector {
max-width: 80px;
background-color: white;
}
.column-type-selector:focus {
outline: none;
border-color: #4f46e5;
}
.column-selected {
background-color: #e0e7ff;
border-left: 3px solid #4f46e5;
}
.data-table-container {
max-height: 500px;
overflow-y: auto;
}
.chart-container {
height: 400px;
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
}
::-webkit-scrollbar-thumb {
background: #c7d2fe;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #a5b4fc;
}
/* Table styling */
.table-wrapper {
overflow-x: auto;
width: 100%;
}
.data-table {
min-width: 100%;
width: auto;
}
.data-table th, .data-table td {
white-space: nowrap;
padding: 0.75rem 1rem;
text-align: left;
border-bottom: 1px solid #e5e7eb;
}
.data-table th {
position: sticky;
top: 0;
background-color: #f9fafb;
z-index: 10;
}
.data-table tr:hover {
background-color: #f3f4f6;
}
.row-number {
color: #6b7280;
font-weight: 500;
}
.pagination-btn {
min-width: 2.5rem;
}
.pagination-btn.active {
background-color: #4f46e5;
color: white;
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<div class="container mx-auto px-4 py-8">
<div class="text-center mb-8">
<h1 class="text-3xl font-bold text-indigo-700 mb-2">Large CSV Analyzer</h1>
<p class="text-gray-600">Upload CSV files up to 5GB, preview data, and generate insightful visualizations</p>
</div>
<!-- File Upload Section -->
<div class="bg-white rounded-lg shadow-md p-6 mb-8">
<div id="upload-container" class="mb-6">
<div id="dropzone" class="dropzone rounded-lg p-12 text-center cursor-pointer">
<div class="flex flex-col items-center justify-center">
<i class="fas fa-cloud-upload-alt text-4xl text-indigo-500 mb-4"></i>
<h3 class="text-xl font-semibold text-gray-700 mb-2">Drag & Drop your CSV file here</h3>
<p class="text-gray-500 mb-4">or</p>
<label for="file-input" class="bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-2 px-6 rounded-md cursor-pointer transition duration-200">
Browse Files
</label>
<input id="file-input" type="file" accept=".csv" class="hidden">
</div>
</div>
<div class="mt-4 text-sm text-gray-500">
<p>Supported file types: .csv (max 5GB)</p>
</div>
</div>
<!-- Upload Progress -->
<div id="upload-progress" class="hidden">
<div class="flex justify-between mb-1">
<span class="text-sm font-medium text-indigo-700" id="filename-display"></span>
<span class="text-sm font-medium text-indigo-700" id="progress-percentage">0%</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2.5">
<div id="progress-bar" class="progress-bar bg-indigo-600 h-2.5 rounded-full" style="width: 0%"></div>
</div>
<div class="flex justify-between mt-2 text-sm text-gray-500">
<span id="uploaded-size">0 MB</span>
<span id="total-size">0 MB</span>
</div>
<div class="mt-4 flex justify-center">
<button id="cancel-upload" class="text-red-600 hover:text-red-800 font-medium flex items-center">
<i class="fas fa-times-circle mr-2"></i> Cancel Upload
</button>
</div>
</div>
</div>
<!-- Data Preview Section -->
<div id="data-preview" class="hidden bg-white rounded-lg shadow-md p-6 mb-8">
<div class="flex justify-between items-center mb-6">
<h2 class="text-xl font-semibold text-gray-800">Data Preview</h2>
<div class="flex gap-2">
<button id="reset-analysis" class="text-indigo-600 hover:text-indigo-800 font-medium flex items-center">
<i class="fas fa-redo-alt mr-2"></i> Reset Analysis
</button>
<button id="load-full-data" class="bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-2 px-4 rounded-md">
<i class="fas fa-database mr-2"></i> Load Full Dataset
</button>
</div>
</div>
<div class="flex flex-col md:flex-row gap-6">
<!-- Column Selector -->
<div class="w-full md:w-1/4">
<div class="bg-gray-50 rounded-lg p-4 border border-gray-200">
<h3 class="font-medium text-gray-700 mb-3">Select Columns</h3>
<div class="space-y-2 max-h-96 overflow-y-auto" id="column-list">
<!-- Columns will be added here dynamically -->
</div>
<div class="mt-4">
<button id="analyze-btn" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-2 px-4 rounded-md disabled:opacity-50 disabled:cursor-not-allowed" disabled>
<i class="fas fa-chart-bar mr-2"></i> Analyze Selected Columns
</button>
</div>
</div>
</div>
<!-- Data Table -->
<div class="w-full md:w-3/4">
<div class="table-wrapper">
<div class="data-table-container border border-gray-200 rounded-lg">
<table class="data-table">
<thead>
<tr id="table-header">
<th class="row-number">#</th>
<!-- Headers will be added here dynamically -->
</tr>
</thead>
<tbody id="table-body">
<!-- Data rows will be added here dynamically -->
</tbody>
</table>
</div>
</div>
<!-- Pagination Controls -->
<div class="flex flex-col sm:flex-row items-center justify-between mt-4">
<div class="text-sm text-gray-500 mb-2 sm:mb-0">
<span id="row-count-display">Showing rows 1-20 of 20</span>
</div>
<div class="flex items-center space-x-1" id="pagination-controls">
<button class="pagination-btn bg-white border border-gray-300 rounded-md px-3 py-1 text-sm font-medium text-gray-700 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed" id="first-page" disabled>
<i class="fas fa-angle-double-left"></i>
</button>
<button class="pagination-btn bg-white border border-gray-300 rounded-md px-3 py-1 text-sm font-medium text-gray-700 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed" id="prev-page" disabled>
<i class="fas fa-angle-left"></i>
</button>
<div class="flex space-x-1" id="page-numbers">
<!-- Page numbers will be added here dynamically -->
</div>
<button class="pagination-btn bg-white border border-gray-300 rounded-md px-3 py-1 text-sm font-medium text-gray-700 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed" id="next-page" disabled>
<i class="fas fa-angle-right"></i>
</button>
<button class="pagination-btn bg-white border border-gray-300 rounded-md px-3 py-1 text-sm font-medium text-gray-700 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed" id="last-page" disabled>
<i class="fas fa-angle-double-right"></i>
</button>
</div>
<div class="flex items-center mt-2 sm:mt-0">
<span class="text-sm text-gray-500 mr-2">Rows per page:</span>
<select id="rows-per-page" class="border border-gray-300 rounded-md px-2 py-1 text-sm">
<option value="20">20</option>
<option value="50">50</option>
<option value="100">100</option>
<option value="200">200</option>
</select>
</div>
</div>
</div>
</div>
</div>
<!-- Analysis Results Section -->
<div id="analysis-results" class="hidden bg-white rounded-lg shadow-md p-6 mb-8">
<h2 class="text-xl font-semibold text-gray-800 mb-6">Analysis Results</h2>
<!-- Statistics Summary -->
<div class="mb-8">
<h3 class="font-medium text-gray-700 mb-3">Summary Statistics</h3>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4" id="stats-summary">
<!-- Statistics cards will be added here dynamically -->
</div>
</div>
<!-- Visualization -->
<div class="mb-6">
<h3 class="font-medium text-gray-700 mb-3">Visualization</h3>
<div class="flex flex-wrap gap-4 mb-4">
<select id="chart-type" class="border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500">
<option value="bar">Bar Chart</option>
<option value="line">Line Chart</option>
<option value="pie">Pie Chart</option>
<option value="scatter">Scatter Plot</option>
<option value="histogram">Histogram</option>
</select>
<button id="update-chart" class="bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-2 px-4 rounded-md">
Update Chart
</button>
<button id="export-chart" class="border border-indigo-600 text-indigo-600 hover:bg-indigo-50 font-medium py-2 px-4 rounded-md">
<i class="fas fa-download mr-2"></i> Export Image
</button>
</div>
<div class="chart-container bg-gray-50 rounded-lg p-4 border border-gray-200">
<canvas id="analysis-chart"></canvas>
</div>
</div>
</div>
</div>
<script>
// Global variables
let csvData = [];
let fullCsvData = [];
let headers = [];
let selectedColumns = [];
let columnTypes = {}; // Track column data types
let chart = null;
let fileReader = null;
let uploadAbortController = null;
const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB chunks for large files
let isFullDataLoaded = false;
// Pagination variables
let currentPage = 1;
let rowsPerPage = 20;
let totalPages = 1;
let visiblePages = 5; // Number of visible page buttons
// DOM elements
const dropzone = document.getElementById('dropzone');
const fileInput = document.getElementById('file-input');
const uploadContainer = document.getElementById('upload-container');
const uploadProgress = document.getElementById('upload-progress');
const progressBar = document.getElementById('progress-bar');
const progressPercentage = document.getElementById('progress-percentage');
const uploadedSize = document.getElementById('uploaded-size');
const totalSize = document.getElementById('total-size');
const filenameDisplay = document.getElementById('filename-display');
const cancelUpload = document.getElementById('cancel-upload');
const dataPreview = document.getElementById('data-preview');
const columnList = document.getElementById('column-list');
const tableHeader = document.getElementById('table-header');
const tableBody = document.getElementById('table-body');
const analyzeBtn = document.getElementById('analyze-btn');
const analysisResults = document.getElementById('analysis-results');
const statsSummary = document.getElementById('stats-summary');
const chartCanvas = document.getElementById('analysis-chart');
const chartTypeSelect = document.getElementById('chart-type');
const updateChartBtn = document.getElementById('update-chart');
const exportChartBtn = document.getElementById('export-chart');
const resetAnalysisBtn = document.getElementById('reset-analysis');
const loadFullDataBtn = document.getElementById('load-full-data');
const rowCountDisplay = document.getElementById('row-count-display');
const firstPageBtn = document.getElementById('first-page');
const prevPageBtn = document.getElementById('prev-page');
const nextPageBtn = document.getElementById('next-page');
const lastPageBtn = document.getElementById('last-page');
const pageNumbersContainer = document.getElementById('page-numbers');
const rowsPerPageSelect = document.getElementById('rows-per-page');
// Event listeners
dropzone.addEventListener('dragover', (e) => {
e.preventDefault();
dropzone.classList.add('active');
});
dropzone.addEventListener('dragleave', () => {
dropzone.classList.remove('active');
});
dropzone.addEventListener('drop', (e) => {
e.preventDefault();
dropzone.classList.remove('active');
if (e.dataTransfer.files.length) {
fileInput.files = e.dataTransfer.files;
handleFileUpload(e.dataTransfer.files[0]);
}
});
fileInput.addEventListener('change', () => {
if (fileInput.files.length) {
handleFileUpload(fileInput.files[0]);
}
});
cancelUpload.addEventListener('click', () => {
if (uploadAbortController) {
uploadAbortController.abort();
}
resetUploadUI();
});
analyzeBtn.addEventListener('click', analyzeSelectedColumns);
updateChartBtn.addEventListener('click', updateChart);
exportChartBtn.addEventListener('click', exportChart);
resetAnalysisBtn.addEventListener('click', resetAnalysis);
loadFullDataBtn.addEventListener('click', loadFullDataset);
// Pagination event listeners
firstPageBtn.addEventListener('click', () => goToPage(1));
prevPageBtn.addEventListener('click', () => goToPage(currentPage - 1));
nextPageBtn.addEventListener('click', () => goToPage(currentPage + 1));
lastPageBtn.addEventListener('click', () => goToPage(totalPages));
rowsPerPageSelect.addEventListener('change', (e) => {
rowsPerPage = parseInt(e.target.value);
currentPage = 1;
updatePagination();
renderTablePage();
});
// Functions
function handleFileUpload(file) {
if (!file.name.endsWith('.csv')) {
showError('Please upload a CSV file');
return;
}
// Reset state
isFullDataLoaded = false;
csvData = [];
fullCsvData = [];
headers = [];
selectedColumns = [];
currentPage = 1;
rowsPerPage = 20;
// Show upload progress UI
uploadContainer.classList.add('hidden');
uploadProgress.classList.remove('hidden');
filenameDisplay.textContent = file.name;
totalSize.textContent = formatFileSize(file.size);
// For files larger than 50MB, use chunked reading
if (file.size > 50 * 1024 * 1024) {
uploadLargeFile(file);
} else {
uploadSmallFile(file);
}
}
function uploadSmallFile(file) {
uploadAbortController = new AbortController();
// Simulate upload progress for demo purposes
let uploaded = 0;
const total = file.size;
const interval = setInterval(() => {
uploaded += Math.min(CHUNK_SIZE / 10, total - uploaded);
updateProgress(uploaded, total);
if (uploaded >= total) {
clearInterval(interval);
parseCSVFile(file, true);
}
}, 100);
}
function uploadLargeFile(file) {
uploadAbortController = new AbortController();
// In a real application, you would send chunks to the server
// This is a simulation for the UI
let uploaded = 0;
const total = file.size;
const chunks = Math.ceil(total / CHUNK_SIZE);
const uploadNextChunk = (chunkIndex) => {
if (uploadAbortController.signal.aborted) return;
const start = chunkIndex * CHUNK_SIZE;
const end = Math.min(start + CHUNK_SIZE, total);
const chunk = file.slice(start, end);
// Simulate chunk upload delay
setTimeout(() => {
uploaded = end;
updateProgress(uploaded, total);
if (chunkIndex < chunks - 1) {
uploadNextChunk(chunkIndex + 1);
} else {
parseCSVFile(file, true);
}
}, 300);
};
uploadNextChunk(0);
}
function convertValue(value, type) {
if (!value) return value;
switch(type) {
case 'number':
return parseFloat(value) || 0;
case 'date':
return new Date(value);
case 'boolean':
return value.toLowerCase() === 'true' || value === '1';
default:
return value.toString();
}
}
function parseCSVFile(file, isPreview = false) {
fileReader = new FileReader();
fileReader.onload = (e) => {
try {
const results = Papa.parse(e.target.result, {
header: true,
skipEmptyLines: true,
preview: isPreview ? 20 : null // Only parse first 20 rows for preview
});
if (results.errors.length > 0) {
showError('Error parsing CSV: ' + results.errors[0].message);
resetUploadUI();
return;
}
// Initialize column types as string by default
headers.forEach(header => {
columnTypes[header] = 'string';
});
if (isPreview) {
csvData = results.data;
fullCsvData = []; // Will be loaded later if needed
} else {
fullCsvData = results.data;
csvData = fullCsvData.slice(0, 20); // Update preview with full data
isFullDataLoaded = true;
loadFullDataBtn.classList.add('hidden');
}
// Add event listeners for type selectors after DOM is updated
setTimeout(() => {
document.querySelectorAll('.column-type-selector').forEach(select => {
select.addEventListener('change', (e) => {
const column = e.target.dataset.column;
columnTypes[column] = e.target.value;
renderTablePage();
});
});
}, 0);
headers = results.meta.fields;
if (csvData.length === 0) {
showError('CSV file is empty or could not be parsed');
resetUploadUI();
return;
}
displayDataPreview();
} catch (error) {
showError('Error parsing CSV: ' + error.message);
resetUploadUI();
}
};
fileReader.onerror = () => {
showError('Error reading file');
resetUploadUI();
};
fileReader.readAsText(file);
}
function loadFullDataset() {
if (fileInput.files.length === 0) return;
// Show loading state
loadFullDataBtn.disabled = true;
loadFullDataBtn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Loading...';
// Re-parse the file but this time without the preview limit
parseCSVFile(fileInput.files[0], false);
}
function displayDataPreview() {
// Show data preview section
uploadContainer.classList.remove('hidden');
uploadProgress.classList.add('hidden');
dataPreview.classList.remove('hidden');
// Calculate total pages
const totalRows = isFullDataLoaded ? fullCsvData.length : csvData.length;
totalPages = Math.ceil(totalRows / rowsPerPage);
// Update row count display
updateRowCountDisplay();
// Show/hide load full data button
if (isFullDataLoaded) {
loadFullDataBtn.classList.add('hidden');
} else {
loadFullDataBtn.classList.remove('hidden');
loadFullDataBtn.disabled = false;
loadFullDataBtn.innerHTML = '<i class="fas fa-database mr-2"></i> Load Full Dataset';
}
// Populate column list
columnList.innerHTML = '';
headers.forEach(header => {
const columnItem = document.createElement('div');
columnItem.className = 'column-selector p-2 rounded-md cursor-pointer flex items-center';
columnItem.innerHTML = `
<input type="checkbox" id="col-${header}" class="hidden column-checkbox">
<label for="col-${header}" class="flex items-center w-full cursor-pointer">
<span class="w-5 h-5 inline-block border border-gray-300 rounded mr-2 flex-shrink-0"></span>
<span class="truncate flex-grow">${header}</span>
<select class="column-type-selector text-xs border rounded px-1 py-0.5 ml-2" data-column="${header}">
<option value="string">Text</option>
<option value="number">Number</option>
<option value="date">Date</option>
<option value="boolean">Boolean</option>
</select>
</label>
`;
// Add click event to the checkbox label
const checkboxLabel = columnItem.querySelector('label');
checkboxLabel.addEventListener('click', (e) => {
e.stopPropagation(); // Prevent event bubbling
toggleColumnSelection(header, columnItem);
});
columnList.appendChild(columnItem);
});
// Populate table headers
tableHeader.innerHTML = '<th class="row-number">#</th>'; // Reset with row number column
headers.forEach(header => {
const th = document.createElement('th');
th.className = 'px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider';
th.textContent = header;
th.dataset.column = header;
tableHeader.appendChild(th);
});
// Set up pagination
updatePagination();
// Render the first page of data
renderTablePage();
}
function renderTablePage() {
// Clear existing table rows
tableBody.innerHTML = '';
// Get the data to display (either full data or preview)
const dataToShow = isFullDataLoaded ? fullCsvData : csvData;
const totalRows = dataToShow.length;
// Calculate start and end index for current page
const startIndex = (currentPage - 1) * rowsPerPage;
const endIndex = Math.min(startIndex + rowsPerPage, totalRows);
// Create rows for the current page
for (let i = startIndex; i < endIndex; i++) {
const row = dataToShow[i];
const tr = document.createElement('tr');
tr.className = i % 2 === 0 ? 'bg-white' : 'bg-gray-50';
// Add row number cell
const rowNumberCell = document.createElement('td');
rowNumberCell.className = 'row-number px-4 py-2 whitespace-nowrap text-sm';
rowNumberCell.textContent = i + 1;
tr.appendChild(rowNumberCell);
// Add data cells
headers.forEach(header => {
const td = document.createElement('td');
td.className = 'px-4 py-2 whitespace-nowrap text-sm text-gray-500';
const value = row[header];
const type = columnTypes[header];
const convertedValue = convertValue(value, type);
if (type === 'date' && convertedValue instanceof Date) {
td.textContent = convertedValue.toLocaleDateString();
} else if (type === 'boolean') {
td.textContent = convertedValue ? '✓' : '✗';
td.style.textAlign = 'center';
} else {
td.textContent = convertedValue || '';
}
tr.appendChild(td);
});
tableBody.appendChild(tr);
}
// Update row count display
updateRowCountDisplay();
}
function updateRowCountDisplay() {
const totalRows = isFullDataLoaded ? fullCsvData.length : csvData.length;
const startRow = (currentPage - 1) * rowsPerPage + 1;
const endRow = Math.min(currentPage * rowsPerPage, totalRows);
rowCountDisplay.textContent = isFullDataLoaded ?
`Showing rows ${startRow}-${endRow} of ${totalRows}` :
`Showing first ${csvData.length} rows of data (${startRow}-${endRow}). Click "Load Full Dataset" to analyze all data.`;
}
function updatePagination() {
const totalRows = isFullDataLoaded ? fullCsvData.length : csvData.length;
totalPages = Math.ceil(totalRows / rowsPerPage);
// Disable/enable pagination buttons
firstPageBtn.disabled = currentPage === 1;
prevPageBtn.disabled = currentPage === 1;
nextPageBtn.disabled = currentPage === totalPages;
lastPageBtn.disabled = currentPage === totalPages;
// Generate page numbers
pageNumbersContainer.innerHTML = '';
// Calculate range of pages to show
let startPage, endPage;
if (totalPages <= visiblePages) {
// Show all pages
startPage = 1;
endPage = totalPages;
} else {
// Calculate start and end pages
const maxPagesBeforeCurrent = Math.floor(visiblePages / 2);
const maxPagesAfterCurrent = Math.ceil(visiblePages / 2) - 1;
if (currentPage <= maxPagesBeforeCurrent) {
// Current page near the start
startPage = 1;
endPage = visiblePages;
} else if (currentPage + maxPagesAfterCurrent >= totalPages) {
// Current page near the end
startPage = totalPages - visiblePages + 1;
endPage = totalPages;
} else {
// Current page somewhere in the middle
startPage = currentPage - maxPagesBeforeCurrent;
endPage = currentPage + maxPagesAfterCurrent;
}
}
// Add page number buttons
for (let i = startPage; i <= endPage; i++) {
const pageBtn = document.createElement('button');
pageBtn.className = `pagination-btn bg-white border border-gray-300 rounded-md px-3 py-1 text-sm font-medium ${i === currentPage ? 'active bg-indigo-600 text-white' : 'text-gray-700 hover:bg-gray-50'}`;
pageBtn.textContent = i;
pageBtn.addEventListener('click', () => goToPage(i));
pageNumbersContainer.appendChild(pageBtn);
}
}
function goToPage(page) {
if (page < 1 || page > totalPages) return;
currentPage = page;
renderTablePage();
updatePagination();
// Scroll to top of table
document.querySelector('.data-table-container').scrollTop = 0;
}
function toggleColumnSelection(columnName, columnElement) {
const index = selectedColumns.indexOf(columnName);
if (index === -1) {
selectedColumns.push(columnName);
columnElement.classList.add('column-selected');
columnElement.querySelector('span:first-child').innerHTML = '<i class="fas fa-check text-indigo-600"></i>';
} else {
selectedColumns.splice(index, 1);
columnElement.classList.remove('column-selected');
columnElement.querySelector('span:first-child').innerHTML = '';
}
// Highlight selected columns in the table
document.querySelectorAll('th[data-column]').forEach(th => {
if (selectedColumns.includes(th.dataset.column)) {
th.classList.add('bg-indigo-50', 'text-indigo-700');
} else {
th.classList.remove('bg-indigo-50', 'text-indigo-700');
}
});
// Enable/disable analyze button
analyzeBtn.disabled = selectedColumns.length === 0;
}
function analyzeSelectedColumns() {
if (selectedColumns.length === 0) return;
// Show analysis results section
analysisResults.classList.remove('hidden');
// Generate statistics
generateStatistics();
// Create initial chart
createChart();
}
function generateStatistics() {
statsSummary.innerHTML = '';
// Use full dataset if loaded, otherwise use preview data
const dataToAnalyze = isFullDataLoaded ? fullCsvData : csvData;
selectedColumns.forEach(column => {
const values = dataToAnalyze.map(row => parseFloat(row[column])).filter(val => !isNaN(val));
if (values.length === 0) {
// Non-numeric column
const uniqueCount = new Set(dataToAnalyze.map(row => row[column])).size;
const card = document.createElement('div');
card.className = 'bg-gray-50 rounded-lg p-4 border border-gray-200';
card.innerHTML = `
<h4 class="font-medium text-gray-700 truncate">${column}</h4>
<div class="mt-2">
<div class="flex justify-between text-sm text-gray-600">
<span>Type:</span>
<span>Categorical</span>
</div>
<div class="flex justify-between text-sm text-gray-600 mt-1">
<span>Unique Values:</span>
<span>${uniqueCount}</span>
</div>
<div class="flex justify-between text-sm text-gray-600 mt-1">
<span>Missing Values:</span>
<span>${dataToAnalyze.filter(row => !row[column]).length}</span>
</div>
<div class="flex justify-between text-sm text-gray-600 mt-1">
<span>Total Rows:</span>
<span>${dataToAnalyze.length}</span>
</div>
</div>
`;
statsSummary.appendChild(card);
} else {
// Numeric column
const sum = values.reduce((a, b) => a + b, 0);
const mean = sum / values.length;
const sorted = [...values].sort((a, b) => a - b);
const median = sorted[Math.floor(sorted.length / 2)];
const min = sorted[0];
const max = sorted[sorted.length - 1];
// Calculate standard deviation
const squaredDiffs = values.map(val => Math.pow(val - mean, 2));
const variance = squaredDiffs.reduce((a, b) => a + b, 0) / values.length;
const stdDev = Math.sqrt(variance);
const card = document.createElement('div');
card.className = 'bg-gray-50 rounded-lg p-4 border border-gray-200';
card.innerHTML = `
<h4 class="font-medium text-gray-700 truncate">${column}</h4>
<div class="mt-2">
<div class="flex justify-between text-sm text-gray-600">
<span>Mean:</span>
<span>${mean.toFixed(2)}</span>
</div>
<div class="flex justify-between text-sm text-gray-600 mt-1">
<span>Median:</span>
<span>${median.toFixed(2)}</span>
</div>
<div class="flex justify-between text-sm text-gray-600 mt-1">
<span>Min/Max:</span>
<span>${min.toFixed(2)} / ${max.toFixed(2)}</span>
</div>
<div class="flex justify-between text-sm text-gray-600 mt-1">
<span>Std Dev:</span>
<span>${stdDev.toFixed(2)}</span>
</div>
<div class="flex justify-between text-sm text-gray-600 mt-1">
<span>Missing Values:</span>
<span>${dataToAnalyze.filter(row => !row[column]).length}</span>
</div>
<div class="flex justify-between text-sm text-gray-600 mt-1">
<span>Total Rows:</span>
<span>${dataToAnalyze.length}</span>
</div>
</div>
`;
statsSummary.appendChild(card);
}
});
}
function createChart() {
const ctx = chartCanvas.getContext('2d');
const chartType = chartTypeSelect.value;
if (chart) {
chart.destroy();
}
// Use full dataset if loaded, otherwise use preview data
const dataToAnalyze = isFullDataLoaded ? fullCsvData : csvData;
// Prepare data based on selected columns
const datasets = [];
const labels = Array.from({ length: dataToAnalyze.length }, (_, i) => `Row ${i + 1}`);
selectedColumns.forEach((column, i) => {
const values = dataToAnalyze.map(row => parseFloat(row[column]));
const isNumeric = values.every(v => !isNaN(v));
if (isNumeric) {
datasets.push({
label: column,
data: values,
backgroundColor: getColor(i, 0.7),
borderColor: getColor(i, 1),
borderWidth: 1
});
} else {
// For categorical data, count occurrences
const valueCounts = {};
dataToAnalyze.forEach(row => {
const val = row[column] || 'Missing';
valueCounts[val] = (valueCounts[val] || 0) + 1;
});
// For pie charts, use the categorical data directly
if (chartType === 'pie' || chartType === 'doughnut') {
datasets.push({
label: column,
data: Object.values(valueCounts),
backgroundColor: Object.keys(valueCounts).map((_, i) => getColor(i, 0.7)),
borderColor: Object.keys(valueCounts).map((_, i) => getColor(i, 1)),
borderWidth: 1
});
} else {
// For other charts, show counts by category
datasets.push({
label: column,
data: Object.values(valueCounts),
backgroundColor: getColor(i, 0.7),
borderColor: getColor(i, 1),
borderWidth: 1
});
}
}
});
// Create chart based on type
if (chartType === 'pie' || chartType === 'doughnut') {
// For pie/doughnut, only show first selected column
const firstColumn = selectedColumns[0];
const valueCounts = {};
dataToAnalyze.forEach(row => {
const val = row[firstColumn] || 'Missing';
valueCounts[val] = (valueCounts[val] || 0) + 1;
});
chart = new Chart(ctx, {
type: chartType,
data: {
labels: Object.keys(valueCounts),
datasets: [{
data: Object.values(valueCounts),
backgroundColor: Object.keys(valueCounts).map((_, i) => getColor(i, 0.7)),
borderColor: '#fff',
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: `Distribution of ${firstColumn}`
}
}
}
});
} else if (chartType === 'scatter') {
// For scatter plot, need exactly 2 numeric columns
const numericColumns = selectedColumns.filter(col => {
const values = dataToAnalyze.map(row => parseFloat(row[col]));
return values.every(v => !isNaN(v));
});
if (numericColumns.length >= 2) {
const xValues = dataToAnalyze.map(row => parseFloat(row[numericColumns[0]]));
const yValues = dataToAnalyze.map(row => parseFloat(row[numericColumns[1]]));
chart = new Chart(ctx, {
type: 'scatter',
data: {
datasets: [{
label: `${numericColumns[0]} vs ${numericColumns[1]}`,
data: xValues.map((x, i) => ({x, y: yValues[i]})),
backgroundColor: getColor(0, 0.7),
borderColor: getColor(0, 1),
borderWidth: 1,
pointRadius: 5
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
title: {
display: true,
text: numericColumns[0]
}
},
y: {
title: {
display: true,
text: numericColumns[1]
}
}
},
plugins: {
title: {
display: true,
text: `Scatter Plot: ${numericColumns[0]} vs ${numericColumns[1]}`
}
}
}
});
} else {
showError('Scatter plot requires at least 2 numeric columns');
}
} else if (chartType === 'histogram') {
// For histogram, need at least 1 numeric column
const numericColumns = selectedColumns.filter(col => {
const values = dataToAnalyze.map(row => parseFloat(row[col]));
return values.every(v => !isNaN(v));
});
if (numericColumns.length > 0) {
const column = numericColumns[0];
const values = dataToAnalyze.map(row => parseFloat(row[column]));
// Calculate optimal bin count using Sturges' formula
const binCount = Math.ceil(Math.log2(values.length) + 1);
// Find min and max values
const min = Math.min(...values);
const max = Math.max(...values);
const range = max - min;
const binSize = range / binCount;
// Create bins
const bins = Array(binCount).fill(0);
values.forEach(val => {
const binIndex = Math.min(Math.floor((val - min) / binSize), binCount - 1);
bins[binIndex]++;
});
// Create labels for bins
const binLabels = Array(binCount).fill().map((_, i) => {
const start = min + i * binSize;
const end = min + (i + 1) * binSize;
return `${start.toFixed(2)} - ${end.toFixed(2)}`;
});
chart = new Chart(ctx, {
type: 'bar',
data: {
labels: binLabels,
datasets: [{
label: `Frequency of ${column}`,
data: bins,
backgroundColor: getColor(0, 0.7),
borderColor: getColor(0, 1),
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: 'Frequency'
}
},
x: {
title: {
display: true,
text: column
}
}
},
plugins: {
title: {
display: true,
text: `Histogram of ${column}`
}
}
}
});
} else {
showError('Histogram requires at least 1 numeric column');
}
} else {
// For bar/line charts
chart = new Chart(ctx, {
type: chartType,
data: {
labels: labels.slice(0, 100), // Limit to 100 labels for performance
datasets: datasets.map((dataset, i) => ({
...dataset,
data: dataset.data.slice(0, 100) // Limit to 100 data points for performance
}))
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true
}
},
plugins: {
title: {
display: true,
text: `Analysis of ${selectedColumns.join(', ')}`
},
tooltip: {
mode: 'index',
intersect: false
}
}
}
});
}
}
function updateChart() {
createChart();
}
function exportChart() {
if (!chart) return;
const link = document.createElement('a');
link.download = 'chart.png';
link.href = chartCanvas.toDataURL('image/png');
link.click();
}
function resetAnalysis() {
selectedColumns = [];
analysisResults.classList.add('hidden');
// Reset column selections
document.querySelectorAll('.column-selector').forEach(el => {
el.classList.remove('column-selected');
el.querySelector('span:first-child').innerHTML = '';
});
// Reset table header highlights
document.querySelectorAll('th[data-column]').forEach(th => {
th.classList.remove('bg-indigo-50', 'text-indigo-700');
});
// Disable analyze button
analyzeBtn.disabled = true;
}
function resetUploadUI() {
uploadContainer.classList.remove('hidden');
uploadProgress.classList.add('hidden');
progressBar.style.width = '0%';
progressPercentage.textContent = '0%';
uploadedSize.textContent = '0 MB';
fileInput.value = '';
if (uploadAbortController) {
uploadAbortController.abort();
uploadAbortController = null;
}
}
function updateProgress(uploaded, total) {
const percentage = Math.round((uploaded / total) * 100);
progressBar.style.width = `${percentage}%`;
progressPercentage.textContent = `${percentage}%`;
uploadedSize.textContent = formatFileSize(uploaded);
}
function formatFileSize(bytes) {
if (bytes < 1024) return bytes + ' B';
else if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
else if (bytes < 1073741824) return (bytes / 1048576).toFixed(1) + ' MB';
else return (bytes / 1073741824).toFixed(1) + ' GB';
}
function getColor(index, opacity = 1) {
const colors = [
`rgba(79, 70, 229, ${opacity})`, // indigo
`rgba(220, 38, 38, ${opacity})`, // red
`rgba(5, 150, 105, ${opacity})`, // emerald
`rgba(234, 88, 12, ${opacity})`, // orange
`rgba(124, 58, 237, ${opacity})`, // purple
`rgba(8, 145, 178, ${opacity})`, // cyan
`rgba(202, 138, 4, ${opacity})`, // amber
`rgba(22, 163, 74, ${opacity})`, // green
`rgba(217, 70, 239, ${opacity})`, // pink
`rgba(239, 68, 68, ${opacity})` // rose
];
return colors[index % colors.length];
}
function showError(message) {
alert(message); // In a real app, you'd use a more elegant error display
}
</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=anzuo/deepsite" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>