datacanvas-explorer / index.html
Shivangsinha's picture
create a website for table visualisation using a csv file. ask user to provide the csv and produce the result which is best show the visualisation with all other info for them.
53a4f20 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DataCanvas Explorer - CSV Visualization</title>
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<script src="https://unpkg.com/feather-icons"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/papaparse@5.4.1/papaparse.min.js"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: {
50: '#f0f9ff',
100: '#e0f2fe',
200: '#bae6fd',
300: '#7dd3fc',
400: '#38bdf8',
500: '#0ea5e9',
600: '#0284c7',
700: '#0369a1',
800: '#075985',
900: '#0c4a6e',
},
secondary: {
50: '#fefce8',
100: '#fef9c3',
200: '#fef08a',
300: '#fde047',
400: '#facc15',
500: '#eab308',
600: '#ca8a04',
700: '#a16207',
800: '#854d0e',
900: '#713f12',
}
}
}
}
}
</script>
<style>
.glass-effect {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.fade-in {
animation: fadeIn 0.8s ease-in;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.table-container {
max-height: 500px;
overflow-y: auto;
}
.chart-container {
position: relative;
height: 300px;
width: 100%;
}
</style>
</head>
<body class="bg-gradient-to-br from-primary-50 to-primary-100 min-h-screen">
<!-- Header -->
<header class="bg-white shadow-sm sticky top-0 z-50">
<div class="container mx-auto px-4 py-4">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-3">
<div class="bg-primary-500 p-2 rounded-lg">
<i data-feather="bar-chart-2" class="text-white w-6 h-6"></i>
</div>
<h1 class="text-2xl font-bold text-gray-800">DataCanvas Explorer</h1>
</div>
<div class="flex items-center space-x-4">
<button id="themeToggle" class="p-2 rounded-full hover:bg-gray-100 transition-colors">
<i data-feather="moon" class="text-gray-600 w-5 h-5"></i>
</button>
</div>
</div>
</div>
</header>
<!-- Main Content -->
<main class="container mx-auto px-4 py-8">
<!-- Upload Section -->
<section id="uploadSection" class="fade-in">
<div class="max-w-4xl mx-auto text-center mb-12">
<div class="glass-effect rounded-2xl p-8 mb-8">
<div class="bg-primary-100 w-20 h-20 rounded-full flex items-center justify-center mx-auto mb-6">
<i data-feather="upload-cloud" class="text-primary-600 w-10 h-10"></i>
</div>
<h2 class="text-3xl font-bold text-gray-800 mb-4">Upload Your CSV File</h2>
<p class="text-gray-600 mb-6 max-w-2xl mx-auto">
Transform your raw data into beautiful, interactive visualizations.
Simply upload your CSV file and let DataCanvas Explorer work its magic.
</p>
<div class="border-2 border-dashed border-primary-300 rounded-xl p-8 mb-6 bg-white">
<input type="file" id="csvFile" accept=".csv" class="hidden">
<div id="dropZone" class="cursor-pointer">
<i data-feather="file" class="text-primary-400 w-12 h-12 mx-auto mb-4"></i>
<p class="text-gray-600 mb-2">Drag & drop your CSV file here</p>
<p class="text-sm text-gray-500 mb-4">or</p>
<button id="browseButton" class="bg-primary-500 hover:bg-primary-600 text-white px-6 py-3 rounded-lg font-medium transition-colors">
Browse Files
</button>
</div>
<p id="fileName" class="text-sm text-gray-500 mt-4 hidden"></p>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 text-left max-w-2xl mx-auto">
<div class="flex items-start space-x-3">
<div class="bg-secondary-100 p-2 rounded-lg mt-1">
<i data-feather="check" class="text-secondary-600 w-4 h-4"></i>
</div>
<div>
<h4 class="font-semibold text-gray-800">Smart Analysis</h4>
<p class="text-sm text-gray-600">Automatic data type detection and cleaning</p>
</div>
</div>
<div class="flex items-start space-x-3">
<div class="bg-secondary-100 p-2 rounded-lg mt-1">
<i data-feather="bar-chart" class="text-secondary-600 w-4 h-4"></i>
</div>
<div>
<h4 class="font-semibold text-gray-800">Multiple Visualizations</h4>
<p class="text-sm text-gray-600">Choose from various chart types and styles</p>
</div>
</div>
<div class="flex items-start space-x-3">
<div class="bg-secondary-100 p-2 rounded-lg mt-1">
<i data-feather="download" class="text-secondary-600 w-4 h-4"></i>
</div>
<div>
<h4 class="font-semibold text-gray-800">Export Ready</h4>
<p class="text-sm text-gray-600">Download your visualizations in high quality</p>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Results Section (Hidden initially) -->
<section id="resultsSection" class="hidden fade-in">
<!-- Data Overview Cards -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
<div class="bg-white rounded-xl p-6 shadow-sm border border-gray-100">
<div class="flex items-center justify-between mb-4">
<h3 class="text-sm font-medium text-gray-500">Total Rows</h3>
<i data-feather="database" class="text-primary-500 w-5 h-5"></i>
</div>
<p id="totalRows" class="text-3xl font-bold text-gray-800">0</p>
</div>
<div class="bg-white rounded-xl p-6 shadow-sm border border-gray-100">
<div class="flex items-center justify-between mb-4">
<h3 class="text-sm font-medium text-gray-500">Total Columns</h3>
<i data-feather="columns" class="text-primary-500 w-5 h-5"></i>
</div>
<p id="totalColumns" class="text-3xl font-bold text-gray-800">0</p>
</div>
<div class="bg-white rounded-xl p-6 shadow-sm border border-gray-100">
<div class="flex items-center justify-between mb-4">
<h3 class="text-sm font-medium text-gray-500">Numeric Columns</h3>
<i data-feather="hash" class="text-primary-500 w-5 h-5"></i>
</div>
<p id="numericColumns" class="text-3xl font-bold text-gray-800">0</p>
</div>
<div class="bg-white rounded-xl p-6 shadow-sm border border-gray-100">
<div class="flex items-center justify-between mb-4">
<h3 class="text-sm font-medium text-gray-500">Text Columns</h3>
<i data-feather="type" class="text-primary-500 w-5 h-5"></i>
</div>
<p id="textColumns" class="text-3xl font-bold text-gray-800">0</p>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8">
<!-- Data Table -->
<div class="bg-white rounded-xl shadow-sm border border-gray-100">
<div class="p-6 border-b border-gray-100">
<div class="flex items-center justify-between">
<h2 class="text-xl font-semibold text-gray-800">Data Table</h2>
<div class="flex space-x-2">
<button id="exportTable" class="bg-primary-500 hover:bg-primary-600 text-white px-4 py-2 rounded-lg text-sm font-medium transition-colors">
<i data-feather="download" class="w-4 h-4 inline mr-2"></i>
Export
</button>
<button id="toggleFilters" class="bg-gray-100 hover:bg-gray-200 text-gray-700 px-4 py-2 rounded-lg text-sm font-medium transition-colors">
<i data-feather="filter" class="w-4 h-4 inline mr-2"></i>
Filters
</button>
</div>
</div>
<div class="table-container p-4">
<table id="dataTable" class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr id="tableHeader"></tr>
</thead>
<tbody id="tableBody" class="bg-white divide-y divide-gray-200"></tbody>
</table>
</div>
</div>
<!-- Chart Selection & Controls -->
<div class="bg-white rounded-xl shadow-sm border border-gray-100">
<div class="p-6 border-b border-gray-100">
<h2 class="text-xl font-semibold text-gray-800">Visualizations</h2>
</div>
<div class="p-6">
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-2">Chart Type</label>
<select id="chartType" class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-primary-500 focus:border-primary-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="area">Area Chart</option>
</select>
</div>
<div class="grid grid-cols-2 gap-4 mb-6">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">X-Axis</label>
<select id="xAxis" class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-primary-500 focus:border-primary-500"></select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Y-Axis</label>
<select id="yAxis" class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-primary-500 focus:border-primary-500"></select>
</div>
</div>
<div class="chart-container">
<canvas id="dataChart"></canvas>
</div>
<div class="flex justify-between items-center mt-6">
<button id="downloadChart" class="bg-secondary-500 hover:bg-secondary-600 text-white px-4 py-2 rounded-lg text-sm font-medium transition-colors">
<i data-feather="download" class="w-4 h-4 inline mr-2"></i>
Download Chart
</button>
<button id="newAnalysis" class="bg-gray-100 hover:bg-gray-200 text-gray-700 px-4 py-2 rounded-lg text-sm font-medium transition-colors">
<i data-feather="refresh-cw" class="w-4 h-4 inline mr-2"></i>
New Analysis
</button>
</div>
</div>
</div>
</div>
<!-- Statistics & Insights -->
<div class="bg-white rounded-xl shadow-sm border border-gray-100 mb-8">
<div class="p-6 border-b border-gray-100">
<h2 class="text-xl font-semibold text-gray-800">Data Insights</h2>
</div>
<div class="p-6">
<div id="insightsContent" class="space-y-4">
<!-- Insights will be populated here -->
</div>
</div>
</div>
</section>
</main>
<!-- Footer -->
<footer class="bg-white border-t border-gray-200 mt-16">
<div class="container mx-auto px-4 py-8">
<div class="flex flex-col md:flex-row justify-between items-center">
<div class="flex items-center space-x-3 mb-4 md:mb-0">
<div class="bg-primary-500 p-2 rounded-lg">
<i data-feather="bar-chart-2" class="text-white w-5 h-5"></i>
</div>
<p class="text-gray-600 text-sm">DataCanvas Explorer - Transform your data into beautiful stories</p>
</div>
</footer>
<script>
// Initialize Feather Icons
feather.replace();
// DOM Elements
const csvFileInput = document.getElementById('csvFile');
const dropZone = document.getElementById('dropZone');
const browseButton = document.getElementById('browseButton');
const fileName = document.getElementById('fileName');
const uploadSection = document.getElementById('uploadSection');
const resultsSection = document.getElementById('resultsSection');
const dataTable = document.getElementById('dataTable');
const tableHeader = document.getElementById('tableHeader');
const tableBody = document.getElementById('tableBody');
const totalRows = document.getElementById('totalRows');
const totalColumns = document.getElementById('totalColumns');
const numericColumns = document.getElementById('numericColumns');
const textColumns = document.getElementById('textColumns');
const chartType = document.getElementById('chartType');
const xAxis = document.getElementById('xAxis');
const yAxis = document.getElementById('yAxis');
const dataChart = document.getElementById('dataChart');
const downloadChart = document.getElementById('downloadChart');
const newAnalysis = document.getElementById('newAnalysis');
const exportTable = document.getElementById('exportTable');
const toggleFilters = document.getElementById('toggleFilters');
const insightsContent = document.getElementById('insightsContent');
const themeToggle = document.getElementById('themeToggle');
let parsedData = null;
let currentChart = null;
// Event Listeners
browseButton.addEventListener('click', () => csvFileInput.click());
csvFileInput.addEventListener('change', handleFileSelect);
dropZone.addEventListener('dragover', handleDragOver);
dropZone.addEventListener('drop', handleFileDrop);
chartType.addEventListener('change', updateChart);
xAxis.addEventListener('change', updateChart);
yAxis.addEventListener('change', updateChart);
downloadChart.addEventListener('click', downloadChartImage);
newAnalysis.addEventListener('click', resetAnalysis);
exportTable.addEventListener('click', exportTableData);
themeToggle.addEventListener('click', toggleTheme);
function handleDragOver(e) {
e.preventDefault();
dropZone.classList.add('border-primary-500', 'bg-primary-50');
}
function handleFileDrop(e) {
e.preventDefault();
dropZone.classList.remove('border-primary-500', 'bg-primary-50');
const files = e.dataTransfer.files;
if (files.length > 0 && files[0].type === 'text/csv') {
processCSVFile(files[0]);
}
}
function handleFileSelect(e) {
const file = e.target.files[0];
if (file && file.type === 'text/csv') {
processCSVFile(file);
}
}
function processCSVFile(file) {
fileName.textContent = `Selected: ${file.name}`;
fileName.classList.remove('hidden');
Papa.parse(file, {
header: true,
dynamicTyping: true,
complete: function(results) {
if (results.errors.length > 0) {
alert('Error parsing CSV file: ' + results.errors[0].message);
return;
}
parsedData = results;
displayResults();
}
});
}
function displayResults() {
// Hide upload section, show results
uploadSection.classList.add('hidden');
resultsSection.classList.remove('hidden');
// Update overview cards
totalRows.textContent = parsedData.data.length;
totalColumns.textContent = parsedData.meta.fields.length;
const numericCols = parsedData.meta.fields.filter(field =>
parsedData.data.some(row => typeof row[field] === 'number')
).length;
numericColumns.textContent = numericCols;
textColumns.textContent = parsedData.meta.fields.length - numericCols;
// Populate table
populateTable();
// Populate chart controls
populateChartControls();
// Generate insights
generateInsights();
// Update chart
updateChart();
}
function populateTable() {
// Clear existing content
tableHeader.innerHTML = '';
tableBody.innerHTML = '';
// Create header
parsedData.meta.fields.forEach(field => {
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 = field;
tableHeader.appendChild(th);
});
// Create rows (limit to first 100 for performance)
const displayData = parsedData.data.slice(0, 100);
displayData.forEach((row, index) => {
const tr = document.createElement('tr');
tr.className = index % 2 === 0 ? 'bg-white' : 'bg-gray-50';
parsedData.meta.fields.forEach(field => {
const td = document.createElement('td');
td.className = 'px-6 py-4 whitespace-nowrap text-sm text-gray-900';
td.textContent = row[field] !== null && row[field] !== undefined ? row[field] : '';
tr.appendChild(td);
});
tableBody.appendChild(tr);
});
}
function populateChartControls() {
// Clear existing options
xAxis.innerHTML = '';
yAxis.innerHTML = '';
// Add options for both selects
parsedData.meta.fields.forEach(field => {
const optionX = document.createElement('option');
optionX.value = field;
optionX.textContent = field;
xAxis.appendChild(optionX);
const optionY = document.createElement('option');
optionY.value = field;
optionY.textContent = field;
yAxis.appendChild(optionY);
});
// Auto-select first numeric column for Y-axis if available
const numericFields = parsedData.meta.fields.filter(field =>
parsedData.data.some(row => typeof row[field] === 'number')
);
if (numericFields.length > 0) {
yAxis.value = numericFields[0];
}
}
function updateChart() {
if (!parsedData) return;
const xField = xAxis.value;
const yField = yAxis.value;
const type = chartType.value;
// Prepare data for chart
const labels = parsedData.data.map(row => row[xField]).slice(0, 50);
const data = parsedData.data.map(row => row[yField]).slice(0, 50);
// Destroy existing chart
if (currentChart) {
currentChart.destroy();
}
// Create new chart
const ctx = dataChart.getContext('2d');
currentChart = new Chart(ctx, {
type: type,
data: {
labels: labels,
datasets: [{
label: `${yField} by ${xField}`,
data: data,
backgroundColor: type === 'pie' ? [
'#0ea5e9', '#eab308', '#10b981', '#ef4444', '#8b5cf6',
'#f97316', '#06b6d4', '#84cc16', '#f59e0b', '#ec4899'
] : '#0ea5e9',
borderColor: '#0ea5e9',
borderWidth: 2,
fill: type === 'area'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'top',
},
tooltip: {
mode: 'index',
intersect: false,
}
},
scales: type !== 'pie' ? {
x: {
display: true,
title: {
display: true,
text: xField
}
},
y: {
display: true,
title: {
display: true,
text: yField
}
}
} : {}
}
});
}
function generateInsights() {
insightsContent.innerHTML = '';
// Basic statistics for numeric columns
const numericFields = parsedData.meta.fields.filter(field =>
parsedData.data.some(row => typeof row[field] === 'number')
);
numericFields.forEach(field => {
const values = parsedData.data.map(row => row[field]).filter(val => val !== null && val !== undefined);
if (values.length > 0) {
const sum = values.reduce((a, b) => a + b, 0);
const avg = sum / values.length;
const max = Math.max(...values);
const min = Math.min(...values);
const insightDiv = document.createElement('div');
insightDiv.className = 'bg-primary-50 rounded-lg p-4';
insightDiv.innerHTML = `
<div class="flex items-center justify-between mb-2">
<h4 class="font-semibold text-gray-800">${field}</h4>
<span class="text-sm text-primary-600 font-medium">Numeric Column</span>
</div>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
<div>
<span class="text-gray-500">Average:</span>
<p class="font-semibold">${avg.toFixed(2)}</p>
</div>
<div>
<span class="text-gray-500">Min/Max:</span>
<p class="font-semibold">${min.toFixed(2)} / ${max.toFixed(2)}</p>
</div>
<div>
<span class="text-gray-500">Range:</span>
<p class="font-semibold">${(max - min).toFixed(2)}</p>
</div>
<div>
<span class="text-gray-500">Count:</span>
<p class="font-semibold">${values.length}</p>
</div>
</div>
`;
insightsContent.appendChild(insightDiv);
}
});
// Add data quality insights
const qualityDiv = document.createElement('div');
qualityDiv.className = 'bg-secondary-50 rounded-lg p-4';
qualityDiv.innerHTML = `
<div class="flex items-center justify-between mb-2">
<h4 class="font-semibold text-gray-800">Data Quality</h4>
<span class="text-sm text-secondary-600 font-medium">Analysis</span>
</div>
<div class="space-y-2 text-sm">
<div class="flex justify-between">
<span class="text-gray-500">Total Records:</span>
<span class="font-semibold">${parsedData.data.length}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-500">Complete Records:</span>
<span class="font-semibold">${calculateCompleteRecords()}%</span>
</div>
</div>
`;
insightsContent.appendChild(qualityDiv);
}
function calculateCompleteRecords() {
if (!parsedData) return 0;
const completeRecords = parsedData.data.filter(row =>
parsedData.meta.fields.every(field => row[field] !== null && row[field] !== undefined)
);
return Math.round((completeRecords.length / parsedData.data.length) * 100);
}
function downloadChartImage() {
if (currentChart) {
const link = document.createElement('a');
link.download = 'datacanvas-chart.png';
link.href = dataChart.toDataURL();
link.click();
}
}
function resetAnalysis() {
uploadSection.classList.remove('hidden');
resultsSection.classList.add('hidden');
csvFileInput.value = '';
fileName.classList.add('hidden');
parsedData = null;
}
function exportTableData() {
if (!parsedData) return;
const csv = Papa.unparse(parsedData.data);
const blob = new Blob([csv], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.download = 'datacanvas-export.csv';
link.href = url;
link.click();
window.URL.revokeObjectURL(url);
}
function toggleTheme() {
const body = document.body;
const isDark = body.classList.contains('dark');
if (isDark) {
body.classList.remove('dark');
body.classList.add('bg-gradient-to-br', 'from-primary-50', 'to-primary-100');
themeToggle.innerHTML = '<i data-feather="moon" class="text-gray-600 w-5 h-5"></i>';
} else {
body.classList.add('dark');
body.classList.remove('bg-gradient-to-br', 'from-primary-50', 'to-primary-100');
themeToggle.innerHTML = '<i data-feather="sun" class="text-gray-600 w-5 h-5"></i>';
}
feather.replace();
}
// Add dark mode styles
const darkModeStyles = `
<style>
.dark body {
background: #1f2937;
color: #f9fafb;
}
.dark .bg-white {
background-color: #374151;
border-color: #4b5563;
}
.dark .text-gray-800 {
color: #f9fafb;
}
.dark .text-gray-600 {
color: #d1d5db;
}
.dark .text-gray-500 {
color: #9ca3af;
}
.dark .bg-gray-100 {
background-color: #4b5563;
}
.dark .border-gray-200,
.dark .border-gray-100 {
border-color: #4b5563;
}
.dark .divide-gray-200 > * + * {
border-color: #4b5563;
}
</style>
`;
document.head.insertAdjacentHTML('beforeend', darkModeStyles);
</script>
</body>
</html>