Spaces:
Running
Running
Add 2 files
Browse files- index.html +943 -532
- prompts.txt +2 -1
index.html
CHANGED
|
@@ -3,49 +3,54 @@
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>DataViz Dashboard</title>
|
| 7 |
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
|
|
| 9 |
<style>
|
| 10 |
.dropzone {
|
| 11 |
border: 2px dashed #3b82f6;
|
| 12 |
-
border-radius: 0.
|
| 13 |
transition: all 0.3s ease;
|
| 14 |
}
|
| 15 |
.dropzone.active {
|
| 16 |
border-color: #10b981;
|
| 17 |
background-color: #f0fdf4;
|
| 18 |
}
|
| 19 |
-
.chart-preview {
|
| 20 |
-
transition: all 0.3s ease;
|
| 21 |
-
}
|
| 22 |
-
.chart-preview:hover {
|
| 23 |
-
transform: scale(1.02);
|
| 24 |
-
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
| 25 |
-
}
|
| 26 |
-
.sidebar {
|
| 27 |
-
transition: all 0.3s ease;
|
| 28 |
-
}
|
| 29 |
-
@media (max-width: 768px) {
|
| 30 |
-
.sidebar {
|
| 31 |
-
transform: translateX(-100%);
|
| 32 |
-
position: absolute;
|
| 33 |
-
z-index: 50;
|
| 34 |
-
height: 100vh;
|
| 35 |
-
}
|
| 36 |
-
.sidebar.open {
|
| 37 |
-
transform: translateX(0);
|
| 38 |
-
}
|
| 39 |
-
}
|
| 40 |
.chart-container {
|
| 41 |
min-height: 400px;
|
| 42 |
background-color: #f8fafc;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
}
|
| 44 |
.data-table {
|
| 45 |
-
max-height:
|
| 46 |
overflow-y: auto;
|
| 47 |
}
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
::-webkit-scrollbar {
|
| 50 |
width: 8px;
|
| 51 |
height: 8px;
|
|
@@ -60,6 +65,45 @@
|
|
| 60 |
::-webkit-scrollbar-thumb:hover {
|
| 61 |
background: #94a3b8;
|
| 62 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
</style>
|
| 64 |
</head>
|
| 65 |
<body class="bg-gray-50 font-sans">
|
|
@@ -71,27 +115,41 @@
|
|
| 71 |
|
| 72 |
<!-- Sidebar -->
|
| 73 |
<div id="sidebar" class="sidebar w-64 bg-white shadow-md flex flex-col">
|
| 74 |
-
<div class="p-4 border-b border-gray-200">
|
| 75 |
-
<
|
| 76 |
-
<
|
| 77 |
-
|
| 78 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
</div>
|
| 80 |
|
| 81 |
-
<div class="p-4 space-y-4">
|
| 82 |
<div>
|
| 83 |
<h3 class="font-medium text-gray-700 mb-2 flex items-center">
|
| 84 |
<i class="fas fa-database mr-2 text-blue-500"></i> Data Sources
|
|
|
|
| 85 |
</h3>
|
| 86 |
<button id="importBtn" class="w-full bg-blue-50 hover:bg-blue-100 text-blue-600 py-2 px-4 rounded-md flex items-center justify-center">
|
| 87 |
<i class="fas fa-file-import mr-2"></i> Import Data
|
| 88 |
</button>
|
| 89 |
<input type="file" id="fileInput" accept=".csv,.xlsx,.xls" class="hidden">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
</div>
|
| 91 |
|
| 92 |
<div>
|
| 93 |
<h3 class="font-medium text-gray-700 mb-2 flex items-center">
|
| 94 |
<i class="fas fa-chart-pie mr-2 text-purple-500"></i> Visualization Types
|
|
|
|
| 95 |
</h3>
|
| 96 |
<div class="space-y-2">
|
| 97 |
<button class="viz-type-btn w-full text-left py-2 px-3 rounded-md hover:bg-purple-50 flex items-center" data-type="bar">
|
|
@@ -118,6 +176,7 @@
|
|
| 118 |
<div>
|
| 119 |
<h3 class="font-medium text-gray-700 mb-2 flex items-center">
|
| 120 |
<i class="fas fa-magic mr-2 text-green-500"></i> AI Suggestions
|
|
|
|
| 121 |
</h3>
|
| 122 |
<button id="aiSuggestBtn" class="w-full bg-green-50 hover:bg-green-100 text-green-600 py-2 px-4 rounded-md flex items-center justify-center">
|
| 123 |
<i class="fas fa-robot mr-2"></i> Get AI Recommendation
|
|
@@ -127,6 +186,7 @@
|
|
| 127 |
<div>
|
| 128 |
<h3 class="font-medium text-gray-700 mb-2 flex items-center">
|
| 129 |
<i class="fas fa-tasks mr-2 text-yellow-500"></i> Data Cleaning
|
|
|
|
| 130 |
</h3>
|
| 131 |
<div class="space-y-2">
|
| 132 |
<button class="data-clean-btn w-full text-left py-2 px-3 rounded-md hover:bg-yellow-50 flex items-center" data-action="remove-duplicates">
|
|
@@ -138,19 +198,23 @@
|
|
| 138 |
<button class="data-clean-btn w-full text-left py-2 px-3 rounded-md hover:bg-yellow-50 flex items-center" data-action="filter-data">
|
| 139 |
<i class="fas fa-filter mr-2 text-yellow-500"></i> Filter Data
|
| 140 |
</button>
|
|
|
|
|
|
|
|
|
|
| 141 |
</div>
|
| 142 |
</div>
|
| 143 |
|
| 144 |
-
<div
|
| 145 |
<h3 class="font-medium text-gray-700 mb-2 flex items-center">
|
| 146 |
-
<i class="fas fa-
|
|
|
|
| 147 |
</h3>
|
| 148 |
-
<select class="w-full p-2 border border-gray-300 rounded-md">
|
| 149 |
-
<option>
|
| 150 |
-
<option>
|
| 151 |
-
<option>
|
| 152 |
-
<option>
|
| 153 |
-
<option>
|
| 154 |
</select>
|
| 155 |
</div>
|
| 156 |
</div>
|
|
@@ -159,6 +223,9 @@
|
|
| 159 |
<button id="exportBtn" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white py-2 px-4 rounded-md flex items-center justify-center">
|
| 160 |
<i class="fas fa-file-export mr-2"></i> Export Dashboard
|
| 161 |
</button>
|
|
|
|
|
|
|
|
|
|
| 162 |
</div>
|
| 163 |
</div>
|
| 164 |
|
|
@@ -195,10 +262,10 @@
|
|
| 195 |
<i class="fas fa-file-import mr-2 text-blue-500"></i> Import Your Data
|
| 196 |
</h3>
|
| 197 |
<div class="flex space-x-2">
|
| 198 |
-
<button class="px-3 py-1 bg-gray-100 hover:bg-gray-200 rounded-md text-sm">
|
| 199 |
<i class="fas fa-question-circle mr-1"></i> Help
|
| 200 |
</button>
|
| 201 |
-
<button class="px-3 py-1 bg-gray-100 hover:bg-gray-200 rounded-md text-sm">
|
| 202 |
<i class="fas fa-history mr-1"></i> Recent Files
|
| 203 |
</button>
|
| 204 |
</div>
|
|
@@ -230,6 +297,9 @@
|
|
| 230 |
<button id="transformDataBtn" class="px-3 py-1 bg-purple-50 hover:bg-purple-100 text-purple-600 rounded-md text-sm flex items-center">
|
| 231 |
<i class="fas fa-exchange-alt mr-1"></i> Transform
|
| 232 |
</button>
|
|
|
|
|
|
|
|
|
|
| 233 |
</div>
|
| 234 |
</div>
|
| 235 |
|
|
@@ -247,102 +317,21 @@
|
|
| 247 |
</div>
|
| 248 |
</div>
|
| 249 |
|
| 250 |
-
<!--
|
| 251 |
-
<div id="
|
| 252 |
<div class="flex justify-between items-center mb-4">
|
| 253 |
<h3 class="text-lg font-medium text-gray-800 flex items-center">
|
| 254 |
-
<i class="fas fa-
|
| 255 |
</h3>
|
| 256 |
<div class="flex space-x-2">
|
| 257 |
-
<button id="
|
| 258 |
-
<i class="fas fa-
|
| 259 |
</button>
|
| 260 |
-
<button id="
|
| 261 |
-
<i class="fas fa-
|
| 262 |
</button>
|
| 263 |
</div>
|
| 264 |
</div>
|
| 265 |
-
|
| 266 |
-
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
| 267 |
-
<!-- Visualization controls -->
|
| 268 |
-
<div class="lg:col-span-1 bg-gray-50 p-4 rounded-lg">
|
| 269 |
-
<div class="space-y-4">
|
| 270 |
-
<div>
|
| 271 |
-
<label class="block text-sm font-medium text-gray-700 mb-1">Chart Type</label>
|
| 272 |
-
<select id="chartTypeSelect" class="w-full p-2 border border-gray-300 rounded-md">
|
| 273 |
-
<option value="bar">Bar Chart</option>
|
| 274 |
-
<option value="line">Line Chart</option>
|
| 275 |
-
<option value="pie">Pie Chart</option>
|
| 276 |
-
<option value="scatter">Scatter Plot</option>
|
| 277 |
-
<option value="flowchart">Flowchart</option>
|
| 278 |
-
<option value="mindmap">Mind Map</option>
|
| 279 |
-
</select>
|
| 280 |
-
</div>
|
| 281 |
-
|
| 282 |
-
<div>
|
| 283 |
-
<label class="block text-sm font-medium text-gray-700 mb-1">X-Axis</label>
|
| 284 |
-
<select id="xAxisSelect" class="w-full p-2 border border-gray-300 rounded-md">
|
| 285 |
-
<!-- Options will be populated by JavaScript -->
|
| 286 |
-
</select>
|
| 287 |
-
</div>
|
| 288 |
-
|
| 289 |
-
<div>
|
| 290 |
-
<label class="block text-sm font-medium text-gray-700 mb-1">Y-Axis</label>
|
| 291 |
-
<select id="yAxisSelect" class="w-full p-2 border border-gray-300 rounded-md">
|
| 292 |
-
<!-- Options will be populated by JavaScript -->
|
| 293 |
-
</select>
|
| 294 |
-
</div>
|
| 295 |
-
|
| 296 |
-
<div>
|
| 297 |
-
<label class="block text-sm font-medium text-gray-700 mb-1">Color Scheme</label>
|
| 298 |
-
<select id="colorSchemeSelect" class="w-full p-2 border border-gray-300 rounded-md">
|
| 299 |
-
<option value="default">Default</option>
|
| 300 |
-
<option value="rainbow">Rainbow</option>
|
| 301 |
-
<option value="pastel">Pastel</option>
|
| 302 |
-
<option value="warm">Warm</option>
|
| 303 |
-
<option value="cool">Cool</option>
|
| 304 |
-
<option value="monochrome">Monochrome</option>
|
| 305 |
-
</select>
|
| 306 |
-
</div>
|
| 307 |
-
|
| 308 |
-
<div>
|
| 309 |
-
<label class="block text-sm font-medium text-gray-700 mb-1">Chart Title</label>
|
| 310 |
-
<input type="text" id="chartTitleInput" class="w-full p-2 border border-gray-300 rounded-md" placeholder="Enter chart title">
|
| 311 |
-
</div>
|
| 312 |
-
|
| 313 |
-
<div class="pt-2">
|
| 314 |
-
<button id="updateChartBtn" class="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-md">
|
| 315 |
-
Update Visualization
|
| 316 |
-
</button>
|
| 317 |
-
</div>
|
| 318 |
-
</div>
|
| 319 |
-
</div>
|
| 320 |
-
|
| 321 |
-
<!-- Visualization preview -->
|
| 322 |
-
<div class="lg:col-span-2">
|
| 323 |
-
<div class="chart-container border border-gray-200 rounded-md p-4 flex items-center justify-center">
|
| 324 |
-
<div id="chartPlaceholder" class="text-center text-gray-500">
|
| 325 |
-
<i class="fas fa-chart-bar text-5xl mb-3"></i>
|
| 326 |
-
<p>Select data and chart type to generate visualization</p>
|
| 327 |
-
</div>
|
| 328 |
-
<canvas id="chartCanvas" class="hidden"></canvas>
|
| 329 |
-
<div id="flowchartContainer" class="hidden w-full h-full"></div>
|
| 330 |
-
<div id="mindmapContainer" class="hidden w-full h-full"></div>
|
| 331 |
-
</div>
|
| 332 |
-
</div>
|
| 333 |
-
</div>
|
| 334 |
-
</div>
|
| 335 |
-
|
| 336 |
-
<!-- AI recommendation section -->
|
| 337 |
-
<div id="aiRecommendationSection" class="bg-white rounded-lg shadow-md p-6 hidden">
|
| 338 |
-
<div class="flex justify-between items-center mb-4">
|
| 339 |
-
<h3 class="text-lg font-medium text-gray-800 flex items-center">
|
| 340 |
-
<i class="fas fa-robot mr-2 text-green-500"></i> AI Recommendation
|
| 341 |
-
</h3>
|
| 342 |
-
<button id="applyAiRecommendationBtn" class="px-3 py-1 bg-green-50 hover:bg-green-100 text-green-600 rounded-md text-sm flex items-center">
|
| 343 |
-
<i class="fas fa-check-circle mr-1"></i> Apply Recommendation
|
| 344 |
-
</button>
|
| 345 |
-
</div>
|
| 346 |
|
| 347 |
<div class="bg-blue-50 p-4 rounded-md">
|
| 348 |
<div class="flex items-start">
|
|
@@ -359,6 +348,31 @@
|
|
| 359 |
</div>
|
| 360 |
</div>
|
| 361 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 362 |
<!-- Dashboard templates -->
|
| 363 |
<div id="templatesSection" class="mt-6 hidden">
|
| 364 |
<div class="flex justify-between items-center mb-4">
|
|
@@ -371,7 +385,7 @@
|
|
| 371 |
</div>
|
| 372 |
|
| 373 |
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
| 374 |
-
<div class="template-card bg-white rounded-lg shadow-md overflow-hidden border border-gray-200 hover:border-blue-300 cursor-pointer">
|
| 375 |
<div class="h-32 bg-gradient-to-r from-blue-400 to-blue-600 flex items-center justify-center">
|
| 376 |
<i class="fas fa-chart-bar text-white text-4xl"></i>
|
| 377 |
</div>
|
|
@@ -381,7 +395,7 @@
|
|
| 381 |
</div>
|
| 382 |
</div>
|
| 383 |
|
| 384 |
-
<div class="template-card bg-white rounded-lg shadow-md overflow-hidden border border-gray-200 hover:border-blue-300 cursor-pointer">
|
| 385 |
<div class="h-32 bg-gradient-to-r from-purple-400 to-purple-600 flex items-center justify-center">
|
| 386 |
<i class="fas fa-users text-white text-4xl"></i>
|
| 387 |
</div>
|
|
@@ -391,7 +405,7 @@
|
|
| 391 |
</div>
|
| 392 |
</div>
|
| 393 |
|
| 394 |
-
<div class="template-card bg-white rounded-lg shadow-md overflow-hidden border border-gray-200 hover:border-blue-300 cursor-pointer">
|
| 395 |
<div class="h-32 bg-gradient-to-r from-green-400 to-green-600 flex items-center justify-center">
|
| 396 |
<i class="fas fa-project-diagram text-white text-4xl"></i>
|
| 397 |
</div>
|
|
@@ -482,19 +496,19 @@
|
|
| 482 |
</div>
|
| 483 |
<div>
|
| 484 |
<label class="block text-sm font-medium text-gray-700 mb-1">Quality</label>
|
| 485 |
-
<select class="w-full p-2 border border-gray-300 rounded-md">
|
| 486 |
-
<option>High (300dpi)</option>
|
| 487 |
-
<option>Medium (150dpi)</option>
|
| 488 |
-
<option>Low (72dpi)</option>
|
| 489 |
</select>
|
| 490 |
</div>
|
| 491 |
<div>
|
| 492 |
<label class="block text-sm font-medium text-gray-700 mb-1">Size</label>
|
| 493 |
-
<select class="w-full p-2 border border-gray-300 rounded-md">
|
| 494 |
-
<option>Original Size</option>
|
| 495 |
-
<option>A4 (210 × 297mm)</option>
|
| 496 |
-
<option>Letter (216 × 279mm)</option>
|
| 497 |
-
<option>Custom...</option>
|
| 498 |
</select>
|
| 499 |
</div>
|
| 500 |
</div>
|
|
@@ -510,22 +524,111 @@
|
|
| 510 |
</div>
|
| 511 |
</div>
|
| 512 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 513 |
<script>
|
| 514 |
// Sample data for demonstration
|
| 515 |
let sampleData = [
|
| 516 |
-
{ id: 1, product: 'Laptop', category: 'Electronics', sales: 1200, profit: 300, region: 'North' },
|
| 517 |
-
{ id: 2, product: 'Smartphone', category: 'Electronics', sales: 800, profit: 200, region: 'South' },
|
| 518 |
-
{ id: 3, product: 'Desk', category: 'Furniture', sales: 450, profit: 150, region: 'East' },
|
| 519 |
-
{ id: 4, product: 'Chair', category: 'Furniture', sales: 250, profit: 75, region: 'West' },
|
| 520 |
-
{ id: 5, product: 'Monitor', category: 'Electronics', sales: 350, profit: 100, region: 'North' },
|
| 521 |
-
{ id: 6, product: 'Keyboard', category: 'Electronics', sales: 100, profit: 25, region: 'South' },
|
| 522 |
-
{ id: 7, product: 'Mouse', category: 'Electronics', sales: 50, profit: 15, region: 'East' },
|
| 523 |
-
{ id: 8, product: 'Table', category: 'Furniture', sales: 600, profit: 180, region: 'West' }
|
| 524 |
];
|
| 525 |
|
| 526 |
// DOM elements
|
| 527 |
const sidebar = document.getElementById('sidebar');
|
| 528 |
const sidebarToggle = document.getElementById('sidebarToggle');
|
|
|
|
| 529 |
const importBtn = document.getElementById('importBtn');
|
| 530 |
const browseFilesBtn = document.getElementById('browseFilesBtn');
|
| 531 |
const fileInput = document.getElementById('fileInput');
|
|
@@ -554,20 +657,47 @@
|
|
| 554 |
const colorSchemeSelect = document.getElementById('colorSchemeSelect');
|
| 555 |
const chartTitleInput = document.getElementById('chartTitleInput');
|
| 556 |
const updateChartBtn = document.getElementById('updateChartBtn');
|
| 557 |
-
const chartPlaceholder = document.getElementById('chartPlaceholder');
|
| 558 |
-
const chartCanvas = document.getElementById('chartCanvas');
|
| 559 |
-
const flowchartContainer = document.getElementById('flowchartContainer');
|
| 560 |
-
const mindmapContainer = document.getElementById('mindmapContainer');
|
| 561 |
const aiSuggestBtn = document.getElementById('aiSuggestBtn');
|
| 562 |
const aiRecommendationText = document.getElementById('aiRecommendationText');
|
| 563 |
const applyAiRecommendationBtn = document.getElementById('applyAiRecommendationBtn');
|
|
|
|
| 564 |
const vizTypeBtns = document.querySelectorAll('.viz-type-btn');
|
| 565 |
const dataCleanBtns = document.querySelectorAll('.data-clean-btn');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 566 |
|
| 567 |
// Current state
|
| 568 |
let currentData = [];
|
| 569 |
-
let
|
| 570 |
let currentVizType = 'bar';
|
|
|
|
|
|
|
|
|
|
| 571 |
|
| 572 |
// Initialize the app
|
| 573 |
function init() {
|
|
@@ -576,6 +706,24 @@
|
|
| 576 |
sidebar.classList.toggle('open');
|
| 577 |
});
|
| 578 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 579 |
// Import button click
|
| 580 |
importBtn.addEventListener('click', () => {
|
| 581 |
fileInput.click();
|
|
@@ -609,13 +757,7 @@
|
|
| 609 |
btn.addEventListener('click', () => {
|
| 610 |
const type = btn.dataset.type;
|
| 611 |
currentVizType = type;
|
| 612 |
-
|
| 613 |
-
updateVizControls();
|
| 614 |
-
if (currentData.length > 0) {
|
| 615 |
-
renderVisualization();
|
| 616 |
-
} else {
|
| 617 |
-
alert('Please import data first');
|
| 618 |
-
}
|
| 619 |
});
|
| 620 |
});
|
| 621 |
|
|
@@ -623,7 +765,13 @@
|
|
| 623 |
dataCleanBtns.forEach(btn => {
|
| 624 |
btn.addEventListener('click', () => {
|
| 625 |
const action = btn.dataset.action;
|
| 626 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 627 |
});
|
| 628 |
});
|
| 629 |
|
|
@@ -638,8 +786,22 @@
|
|
| 638 |
|
| 639 |
saveEditBtn.addEventListener('click', () => {
|
| 640 |
// In a real app, we would save the edited data
|
|
|
|
| 641 |
editDataModal.classList.add('hidden');
|
| 642 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 643 |
});
|
| 644 |
|
| 645 |
// Export modal
|
|
@@ -656,30 +818,92 @@
|
|
| 656 |
});
|
| 657 |
|
| 658 |
confirmExportBtn.addEventListener('click', () => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 659 |
exportModal.classList.add('hidden');
|
| 660 |
-
alert('Export initiated! In a real app, this would download the file.');
|
| 661 |
});
|
| 662 |
|
| 663 |
exportFormatBtns.forEach(btn => {
|
| 664 |
btn.addEventListener('click', () => {
|
| 665 |
-
|
| 666 |
-
|
| 667 |
});
|
| 668 |
});
|
| 669 |
|
| 670 |
-
// Chart controls
|
| 671 |
-
chartTypeSelect.addEventListener('change', () => {
|
| 672 |
-
currentVizType = chartTypeSelect.value;
|
| 673 |
-
updateVizControls();
|
| 674 |
-
});
|
| 675 |
-
|
| 676 |
-
updateChartBtn.addEventListener('click', renderVisualization);
|
| 677 |
-
|
| 678 |
// AI suggestion
|
| 679 |
aiSuggestBtn.addEventListener('click', generateAiRecommendation);
|
| 680 |
|
| 681 |
applyAiRecommendationBtn.addEventListener('click', applyAiRecommendation);
|
| 682 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 683 |
// Load sample data for demo
|
| 684 |
loadSampleData();
|
| 685 |
}
|
|
@@ -687,11 +911,12 @@
|
|
| 687 |
// Load sample data for demonstration
|
| 688 |
function loadSampleData() {
|
| 689 |
currentData = [...sampleData];
|
|
|
|
| 690 |
renderDataPreview();
|
| 691 |
dataPreviewSection.classList.remove('hidden');
|
| 692 |
vizWorkspace.classList.remove('hidden');
|
| 693 |
templatesSection.classList.remove('hidden');
|
| 694 |
-
|
| 695 |
}
|
| 696 |
|
| 697 |
// Handle file selection
|
|
@@ -717,17 +942,86 @@
|
|
| 717 |
|
| 718 |
if (fileType === 'csv') {
|
| 719 |
// In a real app, we would parse the CSV file
|
| 720 |
-
alert(`CSV file "${file.name}" selected.
|
| 721 |
-
loadSampleData();
|
|
|
|
| 722 |
} else if (fileType === 'xlsx' || fileType === 'xls') {
|
| 723 |
// In a real app, we would parse the Excel file
|
| 724 |
-
alert(`Excel file "${file.name}" selected.
|
| 725 |
-
loadSampleData();
|
|
|
|
| 726 |
} else {
|
| 727 |
alert('Unsupported file type. Please upload a CSV or Excel file.');
|
| 728 |
}
|
| 729 |
}
|
| 730 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 731 |
// Prevent default drag and drop behaviors
|
| 732 |
function preventDefaults(e) {
|
| 733 |
e.preventDefault();
|
|
@@ -777,355 +1071,342 @@
|
|
| 777 |
});
|
| 778 |
}
|
| 779 |
|
| 780 |
-
//
|
| 781 |
-
function
|
| 782 |
-
if (currentData.length === 0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 783 |
|
| 784 |
// Get headers from first object
|
| 785 |
const headers = Object.keys(currentData[0]);
|
| 786 |
|
| 787 |
-
//
|
| 788 |
-
|
| 789 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 790 |
|
| 791 |
-
//
|
| 792 |
-
|
| 793 |
-
|
| 794 |
-
xAxisSelect.add(new Option(header, header));
|
| 795 |
-
});
|
| 796 |
-
|
| 797 |
-
// For Y-axis, only include numeric columns
|
| 798 |
-
headers.forEach(header => {
|
| 799 |
-
if (typeof currentData[0][header] === 'number') {
|
| 800 |
-
yAxisSelect.add(new Option(header, header));
|
| 801 |
-
}
|
| 802 |
-
});
|
| 803 |
-
|
| 804 |
-
// Set default selections
|
| 805 |
-
xAxisSelect.value = headers[0];
|
| 806 |
-
yAxisSelect.value = headers.find(h => typeof currentData[0][h] === 'number');
|
| 807 |
-
}
|
| 808 |
-
else if (currentVizType === 'pie') {
|
| 809 |
-
headers.forEach(header => {
|
| 810 |
-
xAxisSelect.add(new Option(header, header));
|
| 811 |
-
});
|
| 812 |
-
|
| 813 |
-
// For pie charts, only include numeric columns for values
|
| 814 |
headers.forEach(header => {
|
| 815 |
-
|
| 816 |
-
|
| 817 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 818 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 819 |
|
| 820 |
-
//
|
| 821 |
-
|
| 822 |
-
|
| 823 |
-
|
| 824 |
-
|
| 825 |
-
|
| 826 |
-
|
| 827 |
-
if (typeof currentData[0][header] === 'number') {
|
| 828 |
-
xAxisSelect.add(new Option(header, header));
|
| 829 |
-
yAxisSelect.add(new Option(header, header));
|
| 830 |
-
}
|
| 831 |
-
});
|
| 832 |
|
| 833 |
-
|
| 834 |
-
|
| 835 |
-
|
| 836 |
-
|
| 837 |
-
}
|
| 838 |
-
else if (currentVizType === 'flowchart' || currentVizType === 'mindmap') {
|
| 839 |
-
// For diagrams, we need different controls
|
| 840 |
-
// Simplified for this demo
|
| 841 |
-
xAxisSelect.innerHTML = '<option value="nodes">Nodes</option>';
|
| 842 |
-
yAxisSelect.innerHTML = '<option value="connections">Connections</option>';
|
| 843 |
-
}
|
| 844 |
}
|
| 845 |
|
| 846 |
-
//
|
| 847 |
-
function
|
| 848 |
-
if (currentData.length === 0)
|
|
|
|
|
|
|
|
|
|
| 849 |
|
| 850 |
-
|
| 851 |
-
|
| 852 |
-
|
| 853 |
-
|
| 854 |
-
|
| 855 |
-
|
| 856 |
-
|
| 857 |
-
|
| 858 |
-
const
|
| 859 |
-
const
|
| 860 |
-
const
|
| 861 |
-
|
| 862 |
-
if (
|
| 863 |
-
|
| 864 |
-
|
| 865 |
-
|
| 866 |
-
|
| 867 |
-
|
| 868 |
-
|
| 869 |
-
|
| 870 |
-
|
| 871 |
-
|
| 872 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 873 |
}
|
| 874 |
|
| 875 |
-
//
|
| 876 |
-
|
| 877 |
-
|
| 878 |
-
|
| 879 |
-
|
| 880 |
-
|
| 881 |
-
|
| 882 |
-
|
| 883 |
-
|
| 884 |
-
};
|
| 885 |
-
|
| 886 |
-
// Show a message about what would be rendered
|
| 887 |
-
chartCanvas.width = chartCanvas.offsetWidth;
|
| 888 |
-
chartCanvas.height = chartCanvas.offsetHeight;
|
| 889 |
-
|
| 890 |
-
ctx.fillStyle = '#f8fafc';
|
| 891 |
-
ctx.fillRect(0, 0, chartCanvas.width, chartCanvas.height);
|
| 892 |
-
|
| 893 |
-
ctx.font = '16px Arial';
|
| 894 |
-
ctx.fillStyle = '#334155';
|
| 895 |
-
ctx.textAlign = 'center';
|
| 896 |
-
ctx.fillText(`${title} (${currentVizType})`, chartCanvas.width / 2, 30);
|
| 897 |
-
|
| 898 |
-
ctx.font = '14px Arial';
|
| 899 |
-
ctx.fillText(`X-Axis: ${xAxis}`, chartCanvas.width / 2, 60);
|
| 900 |
-
ctx.fillText(`Y-Axis: ${yAxis}`, chartCanvas.width / 2, 80);
|
| 901 |
-
|
| 902 |
-
// Draw a simple representation of the chart
|
| 903 |
-
if (currentVizType === 'bar') {
|
| 904 |
-
drawMockBarChart(ctx, labels, data);
|
| 905 |
-
} else if (currentVizType === 'line') {
|
| 906 |
-
drawMockLineChart(ctx, labels, data);
|
| 907 |
-
} else if (currentVizType === 'pie') {
|
| 908 |
-
drawMockPieChart(ctx, labels, data);
|
| 909 |
-
} else if (currentVizType === 'scatter') {
|
| 910 |
-
drawMockScatterPlot(ctx, labels, data);
|
| 911 |
}
|
| 912 |
-
}
|
| 913 |
-
|
| 914 |
-
|
| 915 |
-
|
| 916 |
-
|
| 917 |
-
|
| 918 |
-
|
| 919 |
-
|
| 920 |
-
|
| 921 |
-
|
| 922 |
-
|
| 923 |
-
|
| 924 |
-
`;
|
| 925 |
-
}
|
| 926 |
-
else if (currentVizType === 'mindmap') {
|
| 927 |
-
// Show mindmap container
|
| 928 |
-
mindmapContainer.classList.remove('hidden');
|
| 929 |
-
|
| 930 |
-
// In a real app, we would use a diagramming library
|
| 931 |
-
mindmapContainer.innerHTML = `
|
| 932 |
-
<div class="text-center p-4">
|
| 933 |
-
<i class="fas fa-sitemap text-4xl text-green-500 mb-2"></i>
|
| 934 |
-
<h4 class="text-lg font-medium">Mind Map</h4>
|
| 935 |
-
<p class="text-sm text-gray-600">This would display a mind map based on your data</p>
|
| 936 |
-
</div>
|
| 937 |
-
`;
|
| 938 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 939 |
}
|
| 940 |
|
| 941 |
-
//
|
| 942 |
-
function
|
| 943 |
-
const
|
| 944 |
-
const
|
| 945 |
-
const
|
| 946 |
-
const startY = 100;
|
| 947 |
-
const chartHeight = ctx.canvas.height - 150;
|
| 948 |
-
const chartWidth = ctx.canvas.width - 120;
|
| 949 |
-
|
| 950 |
-
// Draw axes
|
| 951 |
-
ctx.strokeStyle = '#94a3b8';
|
| 952 |
-
ctx.lineWidth = 1;
|
| 953 |
|
| 954 |
-
|
| 955 |
-
|
| 956 |
-
|
| 957 |
-
|
| 958 |
-
ctx.stroke();
|
| 959 |
|
| 960 |
-
//
|
| 961 |
-
|
| 962 |
-
|
| 963 |
-
ctx.lineTo(startX + chartWidth, startY + chartHeight);
|
| 964 |
-
ctx.stroke();
|
| 965 |
|
| 966 |
-
|
| 967 |
-
|
| 968 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 969 |
|
| 970 |
-
|
| 971 |
-
|
| 972 |
-
|
| 973 |
-
|
| 974 |
-
|
| 975 |
-
|
| 976 |
-
|
| 977 |
-
|
| 978 |
-
|
| 979 |
-
|
| 980 |
-
|
| 981 |
-
|
| 982 |
-
|
| 983 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 984 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 985 |
}
|
| 986 |
|
| 987 |
-
//
|
| 988 |
-
function
|
| 989 |
-
|
| 990 |
-
|
| 991 |
-
|
| 992 |
-
|
| 993 |
-
const chartWidth = ctx.canvas.width - 120;
|
| 994 |
|
| 995 |
-
|
| 996 |
-
|
| 997 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 998 |
|
| 999 |
-
|
| 1000 |
-
|
| 1001 |
-
|
| 1002 |
-
|
| 1003 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1004 |
|
| 1005 |
-
|
| 1006 |
-
ctx.beginPath();
|
| 1007 |
-
ctx.moveTo(startX, startY + chartHeight);
|
| 1008 |
-
ctx.lineTo(startX + chartWidth, startY + chartHeight);
|
| 1009 |
-
ctx.stroke();
|
| 1010 |
|
| 1011 |
-
//
|
| 1012 |
-
|
| 1013 |
-
|
| 1014 |
-
ctx.lineWidth = 2;
|
| 1015 |
|
| 1016 |
-
|
| 1017 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1018 |
|
| 1019 |
-
|
| 1020 |
-
|
| 1021 |
-
|
| 1022 |
-
|
| 1023 |
-
|
| 1024 |
-
|
| 1025 |
-
ctx.lineTo(x, y);
|
| 1026 |
}
|
| 1027 |
-
|
| 1028 |
-
// Draw point
|
| 1029 |
-
ctx.fillStyle = '#3b82f6';
|
| 1030 |
-
ctx.beginPath();
|
| 1031 |
-
ctx.arc(x, y, 4, 0, Math.PI * 2);
|
| 1032 |
-
ctx.fill();
|
| 1033 |
-
|
| 1034 |
-
// Draw label
|
| 1035 |
-
ctx.font = '12px Arial';
|
| 1036 |
-
ctx.fillStyle = '#334155';
|
| 1037 |
-
ctx.textAlign = 'center';
|
| 1038 |
-
ctx.fillText(labels[i], x, startY + chartHeight + 20);
|
| 1039 |
-
|
| 1040 |
-
x += pointSpacing;
|
| 1041 |
-
}
|
| 1042 |
|
| 1043 |
-
|
|
|
|
| 1044 |
}
|
| 1045 |
|
| 1046 |
-
//
|
| 1047 |
-
function
|
| 1048 |
-
const
|
| 1049 |
-
const
|
| 1050 |
-
const
|
| 1051 |
-
const total = data.reduce((sum, value) => sum + value, 0);
|
| 1052 |
|
| 1053 |
-
let
|
|
|
|
| 1054 |
|
| 1055 |
-
|
| 1056 |
-
|
| 1057 |
-
|
| 1058 |
-
|
| 1059 |
-
|
| 1060 |
-
ctx.moveTo(centerX, centerY);
|
| 1061 |
-
ctx.arc(centerX, centerY, radius, startAngle, startAngle + sliceAngle);
|
| 1062 |
-
ctx.closePath();
|
| 1063 |
-
ctx.fill();
|
| 1064 |
-
|
| 1065 |
-
// Draw label outside
|
| 1066 |
-
const midAngle = startAngle + sliceAngle / 2;
|
| 1067 |
-
const labelX = centerX + (radius + 20) * Math.cos(midAngle);
|
| 1068 |
-
const labelY = centerY + (radius + 20) * Math.sin(midAngle);
|
| 1069 |
-
|
| 1070 |
-
ctx.font = '12px Arial';
|
| 1071 |
-
ctx.fillStyle = '#334155';
|
| 1072 |
-
ctx.textAlign = 'center';
|
| 1073 |
-
ctx.fillText(labels[i], labelX, labelY);
|
| 1074 |
-
|
| 1075 |
-
startAngle += sliceAngle;
|
| 1076 |
}
|
| 1077 |
-
}
|
| 1078 |
-
|
| 1079 |
-
// Draw a mock scatter plot (for demo purposes)
|
| 1080 |
-
function drawMockScatterPlot(ctx, labels, data) {
|
| 1081 |
-
const maxValue = Math.max(...data);
|
| 1082 |
-
const startX = 60;
|
| 1083 |
-
const startY = 100;
|
| 1084 |
-
const chartHeight = ctx.canvas.height - 150;
|
| 1085 |
-
const chartWidth = ctx.canvas.width - 120;
|
| 1086 |
-
|
| 1087 |
-
// Draw axes
|
| 1088 |
-
ctx.strokeStyle = '#94a3b8';
|
| 1089 |
-
ctx.lineWidth = 1;
|
| 1090 |
|
| 1091 |
-
|
| 1092 |
-
|
| 1093 |
-
ctx.moveTo(startX, startY);
|
| 1094 |
-
ctx.lineTo(startX, startY + chartHeight);
|
| 1095 |
-
ctx.stroke();
|
| 1096 |
|
| 1097 |
-
|
| 1098 |
-
ctx.beginPath();
|
| 1099 |
-
ctx.moveTo(startX, startY + chartHeight);
|
| 1100 |
-
ctx.lineTo(startX + chartWidth, startY + chartHeight);
|
| 1101 |
-
ctx.stroke();
|
| 1102 |
|
| 1103 |
-
|
| 1104 |
-
|
| 1105 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1106 |
|
| 1107 |
-
|
| 1108 |
-
const y = startY + chartHeight - (data[i] / maxValue) * chartHeight;
|
| 1109 |
-
|
| 1110 |
-
ctx.fillStyle = getBarColor(i);
|
| 1111 |
-
ctx.beginPath();
|
| 1112 |
-
ctx.arc(x, y, 6, 0, Math.PI * 2);
|
| 1113 |
-
ctx.fill();
|
| 1114 |
-
|
| 1115 |
-
// Draw label
|
| 1116 |
-
ctx.font = '12px Arial';
|
| 1117 |
-
ctx.fillStyle = '#334155';
|
| 1118 |
-
ctx.textAlign = 'center';
|
| 1119 |
-
ctx.fillText(labels[i], x, startY + chartHeight + 20);
|
| 1120 |
-
|
| 1121 |
-
x += pointSpacing;
|
| 1122 |
-
}
|
| 1123 |
}
|
| 1124 |
|
| 1125 |
-
// Get
|
| 1126 |
-
function
|
| 1127 |
-
const scheme = colorSchemeSelect.value;
|
| 1128 |
-
|
| 1129 |
const schemes = {
|
| 1130 |
default: ['#3b82f6', '#ef4444', '#10b981', '#f59e0b', '#8b5cf6'],
|
| 1131 |
rainbow: ['#ff0000', '#ff7f00', '#ffff00', '#00ff00', '#0000ff', '#4b0082', '#9400d3'],
|
|
@@ -1136,7 +1417,131 @@
|
|
| 1136 |
};
|
| 1137 |
|
| 1138 |
const colors = schemes[scheme] || schemes.default;
|
| 1139 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1140 |
}
|
| 1141 |
|
| 1142 |
// Generate AI recommendation
|
|
@@ -1151,16 +1556,27 @@
|
|
| 1151 |
const headers = Object.keys(currentData[0]);
|
| 1152 |
const numericHeaders = headers.filter(h => typeof currentData[0][h] === 'number');
|
| 1153 |
const categoricalHeaders = headers.filter(h => typeof currentData[0][h] === 'string');
|
|
|
|
| 1154 |
|
| 1155 |
let recommendation = '';
|
| 1156 |
|
| 1157 |
-
if (
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1158 |
recommendation = `A grouped bar chart comparing ${numericHeaders[0]} and ${numericHeaders[1]} across different ${categoricalHeaders[0]} categories would effectively show the relationships in your data.`;
|
| 1159 |
-
|
|
|
|
|
|
|
| 1160 |
recommendation = `A pie chart showing the distribution of ${numericHeaders[0]} by ${categoricalHeaders[0]} would be a good choice to visualize proportions.`;
|
| 1161 |
-
|
|
|
|
|
|
|
| 1162 |
recommendation = `A scatter plot with ${numericHeaders[0]} on the X-axis and ${numericHeaders[1]} on the Y-axis would help identify correlations.`;
|
| 1163 |
-
|
|
|
|
|
|
|
| 1164 |
recommendation = `Based on your data structure, a simple table might be the most effective way to present this information.`;
|
| 1165 |
}
|
| 1166 |
|
|
@@ -1170,52 +1586,47 @@
|
|
| 1170 |
|
| 1171 |
// Apply AI recommendation
|
| 1172 |
function applyAiRecommendation() {
|
| 1173 |
-
|
| 1174 |
-
|
| 1175 |
-
currentVizType = 'bar';
|
| 1176 |
-
chartTypeSelect.value = 'bar';
|
| 1177 |
-
updateVizControls();
|
| 1178 |
-
renderVisualization();
|
| 1179 |
-
|
| 1180 |
-
// Scroll to visualization
|
| 1181 |
-
vizWorkspace.scrollIntoView({ behavior: 'smooth' });
|
| 1182 |
}
|
| 1183 |
|
| 1184 |
-
//
|
| 1185 |
-
function
|
| 1186 |
-
if (
|
| 1187 |
-
|
| 1188 |
-
|
| 1189 |
-
|
| 1190 |
-
|
| 1191 |
-
|
| 1192 |
-
|
| 1193 |
-
|
| 1194 |
-
|
| 1195 |
-
|
| 1196 |
-
|
| 1197 |
-
|
| 1198 |
-
|
| 1199 |
-
|
| 1200 |
-
|
| 1201 |
-
|
| 1202 |
-
|
| 1203 |
-
|
| 1204 |
-
|
| 1205 |
-
|
| 1206 |
-
|
| 1207 |
-
|
| 1208 |
-
// Refresh the preview
|
| 1209 |
-
renderDataPreview();
|
| 1210 |
-
|
| 1211 |
-
// If visualization exists, update it
|
| 1212 |
-
if (!chartPlaceholder.classList.contains('hidden')) {
|
| 1213 |
-
renderVisualization();
|
| 1214 |
}
|
| 1215 |
}
|
| 1216 |
|
| 1217 |
-
//
|
| 1218 |
-
|
| 1219 |
-
|
| 1220 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1221 |
</html>
|
|
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>DataViz Pro | Interactive Dashboard</title>
|
| 7 |
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 9 |
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
| 10 |
<style>
|
| 11 |
.dropzone {
|
| 12 |
border: 2px dashed #3b82f6;
|
| 13 |
+
border-radius: 0.75rem;
|
| 14 |
transition: all 0.3s ease;
|
| 15 |
}
|
| 16 |
.dropzone.active {
|
| 17 |
border-color: #10b981;
|
| 18 |
background-color: #f0fdf4;
|
| 19 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
.chart-container {
|
| 21 |
min-height: 400px;
|
| 22 |
background-color: #f8fafc;
|
| 23 |
+
transition: all 0.3s ease;
|
| 24 |
+
}
|
| 25 |
+
.chart-container:hover {
|
| 26 |
+
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
| 27 |
}
|
| 28 |
.data-table {
|
| 29 |
+
max-height: 400px;
|
| 30 |
overflow-y: auto;
|
| 31 |
}
|
| 32 |
+
.grid-stack-item {
|
| 33 |
+
background: white;
|
| 34 |
+
border-radius: 0.5rem;
|
| 35 |
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
| 36 |
+
}
|
| 37 |
+
.grid-stack-item-content {
|
| 38 |
+
padding: 1rem;
|
| 39 |
+
overflow: hidden;
|
| 40 |
+
}
|
| 41 |
+
.tooltip-icon {
|
| 42 |
+
display: inline-flex;
|
| 43 |
+
align-items: center;
|
| 44 |
+
justify-content: center;
|
| 45 |
+
width: 18px;
|
| 46 |
+
height: 18px;
|
| 47 |
+
border-radius: 50%;
|
| 48 |
+
background-color: #e2e8f0;
|
| 49 |
+
color: #64748b;
|
| 50 |
+
font-size: 12px;
|
| 51 |
+
cursor: help;
|
| 52 |
+
margin-left: 4px;
|
| 53 |
+
}
|
| 54 |
::-webkit-scrollbar {
|
| 55 |
width: 8px;
|
| 56 |
height: 8px;
|
|
|
|
| 65 |
::-webkit-scrollbar-thumb:hover {
|
| 66 |
background: #94a3b8;
|
| 67 |
}
|
| 68 |
+
[data-tooltip] {
|
| 69 |
+
position: relative;
|
| 70 |
+
}
|
| 71 |
+
[data-tooltip]:hover::after {
|
| 72 |
+
content: attr(data-tooltip);
|
| 73 |
+
position: absolute;
|
| 74 |
+
bottom: 100%;
|
| 75 |
+
left: 50%;
|
| 76 |
+
transform: translateX(-50%);
|
| 77 |
+
background: #334155;
|
| 78 |
+
color: white;
|
| 79 |
+
padding: 4px 8px;
|
| 80 |
+
border-radius: 4px;
|
| 81 |
+
font-size: 12px;
|
| 82 |
+
white-space: nowrap;
|
| 83 |
+
z-index: 100;
|
| 84 |
+
}
|
| 85 |
+
.dark-mode {
|
| 86 |
+
background-color: #1e293b;
|
| 87 |
+
color: #f8fafc;
|
| 88 |
+
}
|
| 89 |
+
.dark-mode .bg-white {
|
| 90 |
+
background-color: #334155 !important;
|
| 91 |
+
}
|
| 92 |
+
.dark-mode .text-gray-800 {
|
| 93 |
+
color: #f8fafc !important;
|
| 94 |
+
}
|
| 95 |
+
.dark-mode .text-gray-500 {
|
| 96 |
+
color: #94a3b8 !important;
|
| 97 |
+
}
|
| 98 |
+
.dark-mode .border-gray-200 {
|
| 99 |
+
border-color: #475569 !important;
|
| 100 |
+
}
|
| 101 |
+
.dark-mode .bg-gray-50 {
|
| 102 |
+
background-color: #1e293b !important;
|
| 103 |
+
}
|
| 104 |
+
.dark-mode .chart-container {
|
| 105 |
+
background-color: #1e293b !important;
|
| 106 |
+
}
|
| 107 |
</style>
|
| 108 |
</head>
|
| 109 |
<body class="bg-gray-50 font-sans">
|
|
|
|
| 115 |
|
| 116 |
<!-- Sidebar -->
|
| 117 |
<div id="sidebar" class="sidebar w-64 bg-white shadow-md flex flex-col">
|
| 118 |
+
<div class="p-4 border-b border-gray-200 flex justify-between items-center">
|
| 119 |
+
<div>
|
| 120 |
+
<h1 class="text-2xl font-bold text-blue-600 flex items-center">
|
| 121 |
+
<i class="fas fa-chart-line mr-2"></i> DataViz Pro
|
| 122 |
+
</h1>
|
| 123 |
+
<p class="text-sm text-gray-500">Interactive Dashboard</p>
|
| 124 |
+
</div>
|
| 125 |
+
<button id="themeToggle" class="p-2 rounded-full hover:bg-gray-100">
|
| 126 |
+
<i class="fas fa-moon text-gray-600"></i>
|
| 127 |
+
</button>
|
| 128 |
</div>
|
| 129 |
|
| 130 |
+
<div class="p-4 space-y-4 overflow-y-auto flex-1">
|
| 131 |
<div>
|
| 132 |
<h3 class="font-medium text-gray-700 mb-2 flex items-center">
|
| 133 |
<i class="fas fa-database mr-2 text-blue-500"></i> Data Sources
|
| 134 |
+
<span class="tooltip-icon" data-tooltip="Import your data files">?</span>
|
| 135 |
</h3>
|
| 136 |
<button id="importBtn" class="w-full bg-blue-50 hover:bg-blue-100 text-blue-600 py-2 px-4 rounded-md flex items-center justify-center">
|
| 137 |
<i class="fas fa-file-import mr-2"></i> Import Data
|
| 138 |
</button>
|
| 139 |
<input type="file" id="fileInput" accept=".csv,.xlsx,.xls" class="hidden">
|
| 140 |
+
|
| 141 |
+
<div id="recentFiles" class="mt-3 hidden">
|
| 142 |
+
<h4 class="text-sm font-medium text-gray-700 mb-1">Recent Files</h4>
|
| 143 |
+
<ul class="space-y-1 text-sm text-gray-600">
|
| 144 |
+
<!-- Recent files will be populated here -->
|
| 145 |
+
</ul>
|
| 146 |
+
</div>
|
| 147 |
</div>
|
| 148 |
|
| 149 |
<div>
|
| 150 |
<h3 class="font-medium text-gray-700 mb-2 flex items-center">
|
| 151 |
<i class="fas fa-chart-pie mr-2 text-purple-500"></i> Visualization Types
|
| 152 |
+
<span class="tooltip-icon" data-tooltip="Select chart type">?</span>
|
| 153 |
</h3>
|
| 154 |
<div class="space-y-2">
|
| 155 |
<button class="viz-type-btn w-full text-left py-2 px-3 rounded-md hover:bg-purple-50 flex items-center" data-type="bar">
|
|
|
|
| 176 |
<div>
|
| 177 |
<h3 class="font-medium text-gray-700 mb-2 flex items-center">
|
| 178 |
<i class="fas fa-magic mr-2 text-green-500"></i> AI Suggestions
|
| 179 |
+
<span class="tooltip-icon" data-tooltip="Get AI-powered recommendations">?</span>
|
| 180 |
</h3>
|
| 181 |
<button id="aiSuggestBtn" class="w-full bg-green-50 hover:bg-green-100 text-green-600 py-2 px-4 rounded-md flex items-center justify-center">
|
| 182 |
<i class="fas fa-robot mr-2"></i> Get AI Recommendation
|
|
|
|
| 186 |
<div>
|
| 187 |
<h3 class="font-medium text-gray-700 mb-2 flex items-center">
|
| 188 |
<i class="fas fa-tasks mr-2 text-yellow-500"></i> Data Cleaning
|
| 189 |
+
<span class="tooltip-icon" data-tooltip="Clean and transform your data">?</span>
|
| 190 |
</h3>
|
| 191 |
<div class="space-y-2">
|
| 192 |
<button class="data-clean-btn w-full text-left py-2 px-3 rounded-md hover:bg-yellow-50 flex items-center" data-action="remove-duplicates">
|
|
|
|
| 198 |
<button class="data-clean-btn w-full text-left py-2 px-3 rounded-md hover:bg-yellow-50 flex items-center" data-action="filter-data">
|
| 199 |
<i class="fas fa-filter mr-2 text-yellow-500"></i> Filter Data
|
| 200 |
</button>
|
| 201 |
+
<button class="data-clean-btn w-full text-left py-2 px-3 rounded-md hover:bg-yellow-50 flex items-center" data-action="add-column">
|
| 202 |
+
<i class="fas fa-plus-circle mr-2 text-yellow-500"></i> Add Calculated Column
|
| 203 |
+
</button>
|
| 204 |
</div>
|
| 205 |
</div>
|
| 206 |
|
| 207 |
+
<div>
|
| 208 |
<h3 class="font-medium text-gray-700 mb-2 flex items-center">
|
| 209 |
+
<i class="fas fa-clone mr-2 text-indigo-500"></i> Dashboard Templates
|
| 210 |
+
<span class="tooltip-icon" data-tooltip="Start with a pre-built template">?</span>
|
| 211 |
</h3>
|
| 212 |
+
<select id="templateSelect" class="w-full p-2 border border-gray-300 rounded-md">
|
| 213 |
+
<option value="">Select a template...</option>
|
| 214 |
+
<option value="sales">Sales Dashboard</option>
|
| 215 |
+
<option value="customer">Customer Analytics</option>
|
| 216 |
+
<option value="process">Process Flow</option>
|
| 217 |
+
<option value="financial">Financial Report</option>
|
| 218 |
</select>
|
| 219 |
</div>
|
| 220 |
</div>
|
|
|
|
| 223 |
<button id="exportBtn" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white py-2 px-4 rounded-md flex items-center justify-center">
|
| 224 |
<i class="fas fa-file-export mr-2"></i> Export Dashboard
|
| 225 |
</button>
|
| 226 |
+
<button id="saveBtn" class="w-full mt-2 bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-md flex items-center justify-center">
|
| 227 |
+
<i class="fas fa-save mr-2"></i> Save Dashboard
|
| 228 |
+
</button>
|
| 229 |
</div>
|
| 230 |
</div>
|
| 231 |
|
|
|
|
| 262 |
<i class="fas fa-file-import mr-2 text-blue-500"></i> Import Your Data
|
| 263 |
</h3>
|
| 264 |
<div class="flex space-x-2">
|
| 265 |
+
<button class="px-3 py-1 bg-gray-100 hover:bg-gray-200 rounded-md text-sm flex items-center">
|
| 266 |
<i class="fas fa-question-circle mr-1"></i> Help
|
| 267 |
</button>
|
| 268 |
+
<button id="recentFilesBtn" class="px-3 py-1 bg-gray-100 hover:bg-gray-200 rounded-md text-sm flex items-center">
|
| 269 |
<i class="fas fa-history mr-1"></i> Recent Files
|
| 270 |
</button>
|
| 271 |
</div>
|
|
|
|
| 297 |
<button id="transformDataBtn" class="px-3 py-1 bg-purple-50 hover:bg-purple-100 text-purple-600 rounded-md text-sm flex items-center">
|
| 298 |
<i class="fas fa-exchange-alt mr-1"></i> Transform
|
| 299 |
</button>
|
| 300 |
+
<button id="hideDataBtn" class="px-3 py-1 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-md text-sm flex items-center">
|
| 301 |
+
<i class="fas fa-eye-slash mr-1"></i> Hide
|
| 302 |
+
</button>
|
| 303 |
</div>
|
| 304 |
</div>
|
| 305 |
|
|
|
|
| 317 |
</div>
|
| 318 |
</div>
|
| 319 |
|
| 320 |
+
<!-- AI recommendation section -->
|
| 321 |
+
<div id="aiRecommendationSection" class="mb-6 bg-white rounded-lg shadow-md p-6 hidden">
|
| 322 |
<div class="flex justify-between items-center mb-4">
|
| 323 |
<h3 class="text-lg font-medium text-gray-800 flex items-center">
|
| 324 |
+
<i class="fas fa-robot mr-2 text-green-500"></i> AI Recommendation
|
| 325 |
</h3>
|
| 326 |
<div class="flex space-x-2">
|
| 327 |
+
<button id="applyAiRecommendationBtn" class="px-3 py-1 bg-green-50 hover:bg-green-100 text-green-600 rounded-md text-sm flex items-center">
|
| 328 |
+
<i class="fas fa-check-circle mr-1"></i> Apply
|
| 329 |
</button>
|
| 330 |
+
<button id="dismissAiRecommendationBtn" class="px-3 py-1 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-md text-sm flex items-center">
|
| 331 |
+
<i class="fas fa-times mr-1"></i> Dismiss
|
| 332 |
</button>
|
| 333 |
</div>
|
| 334 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 335 |
|
| 336 |
<div class="bg-blue-50 p-4 rounded-md">
|
| 337 |
<div class="flex items-start">
|
|
|
|
| 348 |
</div>
|
| 349 |
</div>
|
| 350 |
|
| 351 |
+
<!-- Visualization workspace -->
|
| 352 |
+
<div id="vizWorkspace" class="bg-white rounded-lg shadow-md p-6 hidden">
|
| 353 |
+
<div class="flex justify-between items-center mb-4">
|
| 354 |
+
<h3 class="text-lg font-medium text-gray-800 flex items-center">
|
| 355 |
+
<i class="fas fa-chart-area mr-2 text-purple-500"></i> Visualization Workspace
|
| 356 |
+
</h3>
|
| 357 |
+
<div class="flex space-x-2">
|
| 358 |
+
<button id="addNewVizBtn" class="px-3 py-1 bg-blue-50 hover:bg-blue-100 text-blue-600 rounded-md text-sm flex items-center">
|
| 359 |
+
<i class="fas fa-plus mr-1"></i> Add Visualization
|
| 360 |
+
</button>
|
| 361 |
+
<button id="saveVizBtn" class="px-3 py-1 bg-green-50 hover:bg-green-100 text-green-600 rounded-md text-sm flex items-center">
|
| 362 |
+
<i class="fas fa-save mr-1"></i> Save
|
| 363 |
+
</button>
|
| 364 |
+
<button id="resetVizBtn" class="px-3 py-1 bg-red-50 hover:bg-red-100 text-red-600 rounded-md text-sm flex items-center">
|
| 365 |
+
<i class="fas fa-redo mr-1"></i> Reset
|
| 366 |
+
</button>
|
| 367 |
+
</div>
|
| 368 |
+
</div>
|
| 369 |
+
|
| 370 |
+
<!-- Grid container for multiple visualizations -->
|
| 371 |
+
<div id="gridContainer" class="grid-stack">
|
| 372 |
+
<!-- Visualizations will be added here -->
|
| 373 |
+
</div>
|
| 374 |
+
</div>
|
| 375 |
+
|
| 376 |
<!-- Dashboard templates -->
|
| 377 |
<div id="templatesSection" class="mt-6 hidden">
|
| 378 |
<div class="flex justify-between items-center mb-4">
|
|
|
|
| 385 |
</div>
|
| 386 |
|
| 387 |
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
| 388 |
+
<div class="template-card bg-white rounded-lg shadow-md overflow-hidden border border-gray-200 hover:border-blue-300 cursor-pointer" data-template="sales">
|
| 389 |
<div class="h-32 bg-gradient-to-r from-blue-400 to-blue-600 flex items-center justify-center">
|
| 390 |
<i class="fas fa-chart-bar text-white text-4xl"></i>
|
| 391 |
</div>
|
|
|
|
| 395 |
</div>
|
| 396 |
</div>
|
| 397 |
|
| 398 |
+
<div class="template-card bg-white rounded-lg shadow-md overflow-hidden border border-gray-200 hover:border-blue-300 cursor-pointer" data-template="customer">
|
| 399 |
<div class="h-32 bg-gradient-to-r from-purple-400 to-purple-600 flex items-center justify-center">
|
| 400 |
<i class="fas fa-users text-white text-4xl"></i>
|
| 401 |
</div>
|
|
|
|
| 405 |
</div>
|
| 406 |
</div>
|
| 407 |
|
| 408 |
+
<div class="template-card bg-white rounded-lg shadow-md overflow-hidden border border-gray-200 hover:border-blue-300 cursor-pointer" data-template="process">
|
| 409 |
<div class="h-32 bg-gradient-to-r from-green-400 to-green-600 flex items-center justify-center">
|
| 410 |
<i class="fas fa-project-diagram text-white text-4xl"></i>
|
| 411 |
</div>
|
|
|
|
| 496 |
</div>
|
| 497 |
<div>
|
| 498 |
<label class="block text-sm font-medium text-gray-700 mb-1">Quality</label>
|
| 499 |
+
<select id="exportQuality" class="w-full p-2 border border-gray-300 rounded-md">
|
| 500 |
+
<option value="high">High (300dpi)</option>
|
| 501 |
+
<option value="medium">Medium (150dpi)</option>
|
| 502 |
+
<option value="low">Low (72dpi)</option>
|
| 503 |
</select>
|
| 504 |
</div>
|
| 505 |
<div>
|
| 506 |
<label class="block text-sm font-medium text-gray-700 mb-1">Size</label>
|
| 507 |
+
<select id="exportSize" class="w-full p-2 border border-gray-300 rounded-md">
|
| 508 |
+
<option value="original">Original Size</option>
|
| 509 |
+
<option value="a4">A4 (210 × 297mm)</option>
|
| 510 |
+
<option value="letter">Letter (216 × 279mm)</option>
|
| 511 |
+
<option value="custom">Custom...</option>
|
| 512 |
</select>
|
| 513 |
</div>
|
| 514 |
</div>
|
|
|
|
| 524 |
</div>
|
| 525 |
</div>
|
| 526 |
|
| 527 |
+
<!-- Modal for adding calculated column -->
|
| 528 |
+
<div id="addColumnModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
|
| 529 |
+
<div class="bg-white rounded-lg shadow-xl w-full max-w-md">
|
| 530 |
+
<div class="p-4 border-b border-gray-200 flex justify-between items-center">
|
| 531 |
+
<h3 class="text-lg font-medium text-gray-800">Add Calculated Column</h3>
|
| 532 |
+
<button id="closeAddColumnModalBtn" class="text-gray-500 hover:text-gray-700">
|
| 533 |
+
<i class="fas fa-times"></i>
|
| 534 |
+
</button>
|
| 535 |
+
</div>
|
| 536 |
+
<div class="p-4">
|
| 537 |
+
<div class="space-y-4">
|
| 538 |
+
<div>
|
| 539 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">Column Name</label>
|
| 540 |
+
<input type="text" id="newColumnName" class="w-full p-2 border border-gray-300 rounded-md" placeholder="Enter column name">
|
| 541 |
+
</div>
|
| 542 |
+
<div>
|
| 543 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">Formula</label>
|
| 544 |
+
<input type="text" id="columnFormula" class="w-full p-2 border border-gray-300 rounded-md" placeholder="e.g., Sales * 0.2">
|
| 545 |
+
<p class="text-xs text-gray-500 mt-1">Use existing column names in your formula</p>
|
| 546 |
+
</div>
|
| 547 |
+
<div>
|
| 548 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">Data Type</label>
|
| 549 |
+
<select id="columnType" class="w-full p-2 border border-gray-300 rounded-md">
|
| 550 |
+
<option value="number">Number</option>
|
| 551 |
+
<option value="text">Text</option>
|
| 552 |
+
<option value="date">Date</option>
|
| 553 |
+
<option value="boolean">True/False</option>
|
| 554 |
+
</select>
|
| 555 |
+
</div>
|
| 556 |
+
</div>
|
| 557 |
+
</div>
|
| 558 |
+
<div class="p-4 border-t border-gray-200 flex justify-end space-x-3">
|
| 559 |
+
<button id="cancelAddColumnBtn" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50">
|
| 560 |
+
Cancel
|
| 561 |
+
</button>
|
| 562 |
+
<button id="confirmAddColumnBtn" class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700">
|
| 563 |
+
Add Column
|
| 564 |
+
</button>
|
| 565 |
+
</div>
|
| 566 |
+
</div>
|
| 567 |
+
</div>
|
| 568 |
+
|
| 569 |
+
<!-- Modal for filter options -->
|
| 570 |
+
<div id="filterModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
|
| 571 |
+
<div class="bg-white rounded-lg shadow-xl w-full max-w-md">
|
| 572 |
+
<div class="p-4 border-b border-gray-200 flex justify-between items-center">
|
| 573 |
+
<h3 class="text-lg font-medium text-gray-800">Filter Data</h3>
|
| 574 |
+
<button id="closeFilterModalBtn" class="text-gray-500 hover:text-gray-700">
|
| 575 |
+
<i class="fas fa-times"></i>
|
| 576 |
+
</button>
|
| 577 |
+
</div>
|
| 578 |
+
<div class="p-4">
|
| 579 |
+
<div class="space-y-4">
|
| 580 |
+
<div>
|
| 581 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">Column to Filter</label>
|
| 582 |
+
<select id="filterColumn" class="w-full p-2 border border-gray-300 rounded-md">
|
| 583 |
+
<!-- Options will be populated by JavaScript -->
|
| 584 |
+
</select>
|
| 585 |
+
</div>
|
| 586 |
+
<div>
|
| 587 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">Filter Condition</label>
|
| 588 |
+
<select id="filterCondition" class="w-full p-2 border border-gray-300 rounded-md">
|
| 589 |
+
<option value="equals">Equals</option>
|
| 590 |
+
<option value="not-equals">Does not equal</option>
|
| 591 |
+
<option value="contains">Contains</option>
|
| 592 |
+
<option value="greater">Greater than</option>
|
| 593 |
+
<option value="less">Less than</option>
|
| 594 |
+
<option value="empty">Is empty</option>
|
| 595 |
+
<option value="not-empty">Is not empty</option>
|
| 596 |
+
</select>
|
| 597 |
+
</div>
|
| 598 |
+
<div id="filterValueContainer">
|
| 599 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">Value</label>
|
| 600 |
+
<input type="text" id="filterValue" class="w-full p-2 border border-gray-300 rounded-md" placeholder="Enter value">
|
| 601 |
+
</div>
|
| 602 |
+
</div>
|
| 603 |
+
</div>
|
| 604 |
+
<div class="p-4 border-t border-gray-200 flex justify-end space-x-3">
|
| 605 |
+
<button id="cancelFilterBtn" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50">
|
| 606 |
+
Cancel
|
| 607 |
+
</button>
|
| 608 |
+
<button id="applyFilterBtn" class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700">
|
| 609 |
+
Apply Filter
|
| 610 |
+
</button>
|
| 611 |
+
</div>
|
| 612 |
+
</div>
|
| 613 |
+
</div>
|
| 614 |
+
|
| 615 |
<script>
|
| 616 |
// Sample data for demonstration
|
| 617 |
let sampleData = [
|
| 618 |
+
{ id: 1, product: 'Laptop', category: 'Electronics', sales: 1200, profit: 300, region: 'North', date: '2023-01-15' },
|
| 619 |
+
{ id: 2, product: 'Smartphone', category: 'Electronics', sales: 800, profit: 200, region: 'South', date: '2023-01-20' },
|
| 620 |
+
{ id: 3, product: 'Desk', category: 'Furniture', sales: 450, profit: 150, region: 'East', date: '2023-02-05' },
|
| 621 |
+
{ id: 4, product: 'Chair', category: 'Furniture', sales: 250, profit: 75, region: 'West', date: '2023-02-10' },
|
| 622 |
+
{ id: 5, product: 'Monitor', category: 'Electronics', sales: 350, profit: 100, region: 'North', date: '2023-03-01' },
|
| 623 |
+
{ id: 6, product: 'Keyboard', category: 'Electronics', sales: 100, profit: 25, region: 'South', date: '2023-03-15' },
|
| 624 |
+
{ id: 7, product: 'Mouse', category: 'Electronics', sales: 50, profit: 15, region: 'East', date: '2023-04-01' },
|
| 625 |
+
{ id: 8, product: 'Table', category: 'Furniture', sales: 600, profit: 180, region: 'West', date: '2023-04-10' }
|
| 626 |
];
|
| 627 |
|
| 628 |
// DOM elements
|
| 629 |
const sidebar = document.getElementById('sidebar');
|
| 630 |
const sidebarToggle = document.getElementById('sidebarToggle');
|
| 631 |
+
const themeToggle = document.getElementById('themeToggle');
|
| 632 |
const importBtn = document.getElementById('importBtn');
|
| 633 |
const browseFilesBtn = document.getElementById('browseFilesBtn');
|
| 634 |
const fileInput = document.getElementById('fileInput');
|
|
|
|
| 657 |
const colorSchemeSelect = document.getElementById('colorSchemeSelect');
|
| 658 |
const chartTitleInput = document.getElementById('chartTitleInput');
|
| 659 |
const updateChartBtn = document.getElementById('updateChartBtn');
|
|
|
|
|
|
|
|
|
|
|
|
|
| 660 |
const aiSuggestBtn = document.getElementById('aiSuggestBtn');
|
| 661 |
const aiRecommendationText = document.getElementById('aiRecommendationText');
|
| 662 |
const applyAiRecommendationBtn = document.getElementById('applyAiRecommendationBtn');
|
| 663 |
+
const dismissAiRecommendationBtn = document.getElementById('dismissAiRecommendationBtn');
|
| 664 |
const vizTypeBtns = document.querySelectorAll('.viz-type-btn');
|
| 665 |
const dataCleanBtns = document.querySelectorAll('.data-clean-btn');
|
| 666 |
+
const addNewVizBtn = document.getElementById('addNewVizBtn');
|
| 667 |
+
const saveVizBtn = document.getElementById('saveVizBtn');
|
| 668 |
+
const resetVizBtn = document.getElementById('resetVizBtn');
|
| 669 |
+
const editDataBtn = document.getElementById('editDataBtn');
|
| 670 |
+
const transformDataBtn = document.getElementById('transformDataBtn');
|
| 671 |
+
const hideDataBtn = document.getElementById('hideDataBtn');
|
| 672 |
+
const recentFilesBtn = document.getElementById('recentFilesBtn');
|
| 673 |
+
const recentFiles = document.getElementById('recentFiles');
|
| 674 |
+
const templateSelect = document.getElementById('templateSelect');
|
| 675 |
+
const templateCards = document.querySelectorAll('.template-card');
|
| 676 |
+
const gridContainer = document.getElementById('gridContainer');
|
| 677 |
+
const saveBtn = document.getElementById('saveBtn');
|
| 678 |
+
const addColumnModal = document.getElementById('addColumnModal');
|
| 679 |
+
const closeAddColumnModalBtn = document.getElementById('closeAddColumnModalBtn');
|
| 680 |
+
const cancelAddColumnBtn = document.getElementById('cancelAddColumnBtn');
|
| 681 |
+
const confirmAddColumnBtn = document.getElementById('confirmAddColumnBtn');
|
| 682 |
+
const newColumnName = document.getElementById('newColumnName');
|
| 683 |
+
const columnFormula = document.getElementById('columnFormula');
|
| 684 |
+
const columnType = document.getElementById('columnType');
|
| 685 |
+
const filterModal = document.getElementById('filterModal');
|
| 686 |
+
const closeFilterModalBtn = document.getElementById('closeFilterModalBtn');
|
| 687 |
+
const filterColumn = document.getElementById('filterColumn');
|
| 688 |
+
const filterCondition = document.getElementById('filterCondition');
|
| 689 |
+
const filterValue = document.getElementById('filterValue');
|
| 690 |
+
const filterValueContainer = document.getElementById('filterValueContainer');
|
| 691 |
+
const cancelFilterBtn = document.getElementById('cancelFilterBtn');
|
| 692 |
+
const applyFilterBtn = document.getElementById('applyFilterBtn');
|
| 693 |
|
| 694 |
// Current state
|
| 695 |
let currentData = [];
|
| 696 |
+
let originalData = [];
|
| 697 |
let currentVizType = 'bar';
|
| 698 |
+
let currentCharts = [];
|
| 699 |
+
let isDarkMode = false;
|
| 700 |
+
let nextVizId = 1;
|
| 701 |
|
| 702 |
// Initialize the app
|
| 703 |
function init() {
|
|
|
|
| 706 |
sidebar.classList.toggle('open');
|
| 707 |
});
|
| 708 |
|
| 709 |
+
// Toggle dark mode
|
| 710 |
+
themeToggle.addEventListener('click', () => {
|
| 711 |
+
isDarkMode = !isDarkMode;
|
| 712 |
+
document.body.classList.toggle('dark-mode', isDarkMode);
|
| 713 |
+
themeToggle.innerHTML = isDarkMode ?
|
| 714 |
+
'<i class="fas fa-sun text-yellow-400"></i>' :
|
| 715 |
+
'<i class="fas fa-moon text-gray-600"></i>';
|
| 716 |
+
|
| 717 |
+
// Update charts for dark mode
|
| 718 |
+
currentCharts.forEach(chart => {
|
| 719 |
+
if (chart.chart) {
|
| 720 |
+
chart.chart.options.scales.x.grid.color = isDarkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)';
|
| 721 |
+
chart.chart.options.scales.y.grid.color = isDarkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)';
|
| 722 |
+
chart.chart.update();
|
| 723 |
+
}
|
| 724 |
+
});
|
| 725 |
+
});
|
| 726 |
+
|
| 727 |
// Import button click
|
| 728 |
importBtn.addEventListener('click', () => {
|
| 729 |
fileInput.click();
|
|
|
|
| 757 |
btn.addEventListener('click', () => {
|
| 758 |
const type = btn.dataset.type;
|
| 759 |
currentVizType = type;
|
| 760 |
+
addVisualizationToGrid();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 761 |
});
|
| 762 |
});
|
| 763 |
|
|
|
|
| 765 |
dataCleanBtns.forEach(btn => {
|
| 766 |
btn.addEventListener('click', () => {
|
| 767 |
const action = btn.dataset.action;
|
| 768 |
+
if (action === 'add-column') {
|
| 769 |
+
showAddColumnModal();
|
| 770 |
+
} else if (action === 'filter-data') {
|
| 771 |
+
showFilterModal();
|
| 772 |
+
} else {
|
| 773 |
+
performDataCleaning(action);
|
| 774 |
+
}
|
| 775 |
});
|
| 776 |
});
|
| 777 |
|
|
|
|
| 786 |
|
| 787 |
saveEditBtn.addEventListener('click', () => {
|
| 788 |
// In a real app, we would save the edited data
|
| 789 |
+
saveEditedData();
|
| 790 |
editDataModal.classList.add('hidden');
|
| 791 |
+
updateAllVisualizations();
|
| 792 |
+
});
|
| 793 |
+
|
| 794 |
+
// Edit data button
|
| 795 |
+
editDataBtn.addEventListener('click', showEditDataModal);
|
| 796 |
+
|
| 797 |
+
// Transform data button
|
| 798 |
+
transformDataBtn.addEventListener('click', () => {
|
| 799 |
+
alert('In a real app, this would show data transformation options');
|
| 800 |
+
});
|
| 801 |
+
|
| 802 |
+
// Hide data button
|
| 803 |
+
hideDataBtn.addEventListener('click', () => {
|
| 804 |
+
dataPreviewSection.classList.add('hidden');
|
| 805 |
});
|
| 806 |
|
| 807 |
// Export modal
|
|
|
|
| 818 |
});
|
| 819 |
|
| 820 |
confirmExportBtn.addEventListener('click', () => {
|
| 821 |
+
const format = document.querySelector('.export-format-btn.active')?.dataset.format || 'png';
|
| 822 |
+
const quality = document.getElementById('exportQuality').value;
|
| 823 |
+
const size = document.getElementById('exportSize').value;
|
| 824 |
+
|
| 825 |
+
exportDashboard(format, quality, size);
|
| 826 |
exportModal.classList.add('hidden');
|
|
|
|
| 827 |
});
|
| 828 |
|
| 829 |
exportFormatBtns.forEach(btn => {
|
| 830 |
btn.addEventListener('click', () => {
|
| 831 |
+
exportFormatBtns.forEach(b => b.classList.remove('border-blue-500', 'bg-blue-50'));
|
| 832 |
+
btn.classList.add('border-blue-500', 'bg-blue-50');
|
| 833 |
});
|
| 834 |
});
|
| 835 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 836 |
// AI suggestion
|
| 837 |
aiSuggestBtn.addEventListener('click', generateAiRecommendation);
|
| 838 |
|
| 839 |
applyAiRecommendationBtn.addEventListener('click', applyAiRecommendation);
|
| 840 |
|
| 841 |
+
dismissAiRecommendationBtn.addEventListener('click', () => {
|
| 842 |
+
aiRecommendationSection.classList.add('hidden');
|
| 843 |
+
});
|
| 844 |
+
|
| 845 |
+
// Add new visualization button
|
| 846 |
+
addNewVizBtn.addEventListener('click', () => {
|
| 847 |
+
addVisualizationToGrid();
|
| 848 |
+
});
|
| 849 |
+
|
| 850 |
+
// Save visualization button
|
| 851 |
+
saveVizBtn.addEventListener('click', () => {
|
| 852 |
+
alert('Visualization saved! In a real app, this would persist the configuration.');
|
| 853 |
+
});
|
| 854 |
+
|
| 855 |
+
// Reset visualization button
|
| 856 |
+
resetVizBtn.addEventListener('click', () => {
|
| 857 |
+
if (confirm('Are you sure you want to reset all visualizations?')) {
|
| 858 |
+
resetVisualizations();
|
| 859 |
+
}
|
| 860 |
+
});
|
| 861 |
+
|
| 862 |
+
// Recent files button
|
| 863 |
+
recentFilesBtn.addEventListener('click', () => {
|
| 864 |
+
recentFiles.classList.toggle('hidden');
|
| 865 |
+
});
|
| 866 |
+
|
| 867 |
+
// Template select
|
| 868 |
+
templateSelect.addEventListener('change', () => {
|
| 869 |
+
if (templateSelect.value) {
|
| 870 |
+
loadTemplate(templateSelect.value);
|
| 871 |
+
}
|
| 872 |
+
});
|
| 873 |
+
|
| 874 |
+
// Template cards
|
| 875 |
+
templateCards.forEach(card => {
|
| 876 |
+
card.addEventListener('click', () => {
|
| 877 |
+
const template = card.dataset.template;
|
| 878 |
+
loadTemplate(template);
|
| 879 |
+
});
|
| 880 |
+
});
|
| 881 |
+
|
| 882 |
+
// Save dashboard button
|
| 883 |
+
saveBtn.addEventListener('click', saveDashboard);
|
| 884 |
+
|
| 885 |
+
// Add column modal
|
| 886 |
+
closeAddColumnModalBtn.addEventListener('click', () => {
|
| 887 |
+
addColumnModal.classList.add('hidden');
|
| 888 |
+
});
|
| 889 |
+
|
| 890 |
+
cancelAddColumnBtn.addEventListener('click', () => {
|
| 891 |
+
addColumnModal.classList.add('hidden');
|
| 892 |
+
});
|
| 893 |
+
|
| 894 |
+
confirmAddColumnBtn.addEventListener('click', addCalculatedColumn);
|
| 895 |
+
|
| 896 |
+
// Filter modal
|
| 897 |
+
closeFilterModalBtn.addEventListener('click', () => {
|
| 898 |
+
filterModal.classList.add('hidden');
|
| 899 |
+
});
|
| 900 |
+
|
| 901 |
+
cancelFilterBtn.addEventListener('click', () => {
|
| 902 |
+
filterModal.classList.add('hidden');
|
| 903 |
+
});
|
| 904 |
+
|
| 905 |
+
applyFilterBtn.addEventListener('click', applyDataFilter);
|
| 906 |
+
|
| 907 |
// Load sample data for demo
|
| 908 |
loadSampleData();
|
| 909 |
}
|
|
|
|
| 911 |
// Load sample data for demonstration
|
| 912 |
function loadSampleData() {
|
| 913 |
currentData = [...sampleData];
|
| 914 |
+
originalData = [...sampleData];
|
| 915 |
renderDataPreview();
|
| 916 |
dataPreviewSection.classList.remove('hidden');
|
| 917 |
vizWorkspace.classList.remove('hidden');
|
| 918 |
templatesSection.classList.remove('hidden');
|
| 919 |
+
updateRecentFilesList();
|
| 920 |
}
|
| 921 |
|
| 922 |
// Handle file selection
|
|
|
|
| 942 |
|
| 943 |
if (fileType === 'csv') {
|
| 944 |
// In a real app, we would parse the CSV file
|
| 945 |
+
alert(`CSV file "${file.name}" selected. For this demo, we'll load sample data.`);
|
| 946 |
+
loadSampleData();
|
| 947 |
+
addToRecentFiles(file.name, 'csv');
|
| 948 |
} else if (fileType === 'xlsx' || fileType === 'xls') {
|
| 949 |
// In a real app, we would parse the Excel file
|
| 950 |
+
alert(`Excel file "${file.name}" selected. For this demo, we'll load sample data.`);
|
| 951 |
+
loadSampleData();
|
| 952 |
+
addToRecentFiles(file.name, 'xlsx');
|
| 953 |
} else {
|
| 954 |
alert('Unsupported file type. Please upload a CSV or Excel file.');
|
| 955 |
}
|
| 956 |
}
|
| 957 |
|
| 958 |
+
// Add to recent files list
|
| 959 |
+
function addToRecentFiles(filename, type) {
|
| 960 |
+
const recentFilesList = JSON.parse(localStorage.getItem('recentFiles') || '[]');
|
| 961 |
+
|
| 962 |
+
// Remove if already exists
|
| 963 |
+
const existingIndex = recentFilesList.findIndex(f => f.name === filename);
|
| 964 |
+
if (existingIndex >= 0) {
|
| 965 |
+
recentFilesList.splice(existingIndex, 1);
|
| 966 |
+
}
|
| 967 |
+
|
| 968 |
+
// Add to beginning
|
| 969 |
+
recentFilesList.unshift({
|
| 970 |
+
name: filename,
|
| 971 |
+
type: type,
|
| 972 |
+
date: new Date().toISOString()
|
| 973 |
+
});
|
| 974 |
+
|
| 975 |
+
// Keep only last 5 files
|
| 976 |
+
if (recentFilesList.length > 5) {
|
| 977 |
+
recentFilesList.pop();
|
| 978 |
+
}
|
| 979 |
+
|
| 980 |
+
localStorage.setItem('recentFiles', JSON.stringify(recentFilesList));
|
| 981 |
+
updateRecentFilesList();
|
| 982 |
+
}
|
| 983 |
+
|
| 984 |
+
// Update recent files list in UI
|
| 985 |
+
function updateRecentFilesList() {
|
| 986 |
+
const recentFilesList = JSON.parse(localStorage.getItem('recentFiles') || '[]');
|
| 987 |
+
const recentFilesListEl = document.querySelector('#recentFiles ul');
|
| 988 |
+
|
| 989 |
+
if (recentFilesList.length === 0) {
|
| 990 |
+
recentFiles.classList.add('hidden');
|
| 991 |
+
return;
|
| 992 |
+
}
|
| 993 |
+
|
| 994 |
+
recentFilesListEl.innerHTML = '';
|
| 995 |
+
|
| 996 |
+
recentFilesList.forEach(file => {
|
| 997 |
+
const li = document.createElement('li');
|
| 998 |
+
li.className = 'flex items-center justify-between hover:bg-gray-100 p-1 rounded';
|
| 999 |
+
|
| 1000 |
+
const icon = document.createElement('i');
|
| 1001 |
+
icon.className = file.type === 'csv' ? 'fas fa-file-csv text-blue-500 mr-2' : 'fas fa-file-excel text-green-500 mr-2';
|
| 1002 |
+
|
| 1003 |
+
const nameSpan = document.createElement('span');
|
| 1004 |
+
nameSpan.textContent = file.name;
|
| 1005 |
+
nameSpan.className = 'truncate flex-1';
|
| 1006 |
+
|
| 1007 |
+
const dateSpan = document.createElement('span');
|
| 1008 |
+
dateSpan.textContent = new Date(file.date).toLocaleDateString();
|
| 1009 |
+
dateSpan.className = 'text-xs text-gray-500 ml-2';
|
| 1010 |
+
|
| 1011 |
+
li.appendChild(icon);
|
| 1012 |
+
li.appendChild(nameSpan);
|
| 1013 |
+
li.appendChild(dateSpan);
|
| 1014 |
+
|
| 1015 |
+
li.addEventListener('click', () => {
|
| 1016 |
+
alert(`Loading ${file.name}. In a real app, we would load this file.`);
|
| 1017 |
+
});
|
| 1018 |
+
|
| 1019 |
+
recentFilesListEl.appendChild(li);
|
| 1020 |
+
});
|
| 1021 |
+
|
| 1022 |
+
recentFiles.classList.remove('hidden');
|
| 1023 |
+
}
|
| 1024 |
+
|
| 1025 |
// Prevent default drag and drop behaviors
|
| 1026 |
function preventDefaults(e) {
|
| 1027 |
e.preventDefault();
|
|
|
|
| 1071 |
});
|
| 1072 |
}
|
| 1073 |
|
| 1074 |
+
// Show edit data modal
|
| 1075 |
+
function showEditDataModal() {
|
| 1076 |
+
if (currentData.length === 0) {
|
| 1077 |
+
alert('No data to edit');
|
| 1078 |
+
return;
|
| 1079 |
+
}
|
| 1080 |
+
|
| 1081 |
+
// Clear existing content
|
| 1082 |
+
editTableHeaders.innerHTML = '';
|
| 1083 |
+
editTableBody.innerHTML = '';
|
| 1084 |
|
| 1085 |
// Get headers from first object
|
| 1086 |
const headers = Object.keys(currentData[0]);
|
| 1087 |
|
| 1088 |
+
// Create header row
|
| 1089 |
+
headers.forEach(header => {
|
| 1090 |
+
const th = document.createElement('th');
|
| 1091 |
+
th.className = 'px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider';
|
| 1092 |
+
th.textContent = header;
|
| 1093 |
+
editTableHeaders.appendChild(th);
|
| 1094 |
+
});
|
| 1095 |
|
| 1096 |
+
// Create editable data rows
|
| 1097 |
+
currentData.forEach((row, rowIndex) => {
|
| 1098 |
+
const tr = document.createElement('tr');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1099 |
headers.forEach(header => {
|
| 1100 |
+
const td = document.createElement('td');
|
| 1101 |
+
td.className = 'px-6 py-2 whitespace-nowrap';
|
| 1102 |
+
|
| 1103 |
+
const input = document.createElement('input');
|
| 1104 |
+
input.type = 'text';
|
| 1105 |
+
input.className = 'w-full p-1 border border-gray-300 rounded text-sm';
|
| 1106 |
+
input.value = row[header];
|
| 1107 |
+
input.dataset.column = header;
|
| 1108 |
+
input.dataset.row = rowIndex;
|
| 1109 |
+
|
| 1110 |
+
td.appendChild(input);
|
| 1111 |
+
tr.appendChild(td);
|
| 1112 |
});
|
| 1113 |
+
editTableBody.appendChild(tr);
|
| 1114 |
+
});
|
| 1115 |
+
|
| 1116 |
+
editDataModal.classList.remove('hidden');
|
| 1117 |
+
}
|
| 1118 |
+
|
| 1119 |
+
// Save edited data
|
| 1120 |
+
function saveEditedData() {
|
| 1121 |
+
const inputs = editTableBody.querySelectorAll('input');
|
| 1122 |
+
|
| 1123 |
+
inputs.forEach(input => {
|
| 1124 |
+
const rowIndex = parseInt(input.dataset.row);
|
| 1125 |
+
const column = input.dataset.column;
|
| 1126 |
+
const value = input.value;
|
| 1127 |
|
| 1128 |
+
// Convert to appropriate type
|
| 1129 |
+
let convertedValue = value;
|
| 1130 |
+
if (originalData[rowIndex] && typeof originalData[rowIndex][column] === 'number') {
|
| 1131 |
+
convertedValue = parseFloat(value) || 0;
|
| 1132 |
+
} else if (originalData[rowIndex] && originalData[rowIndex][column] instanceof Date) {
|
| 1133 |
+
convertedValue = new Date(value);
|
| 1134 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1135 |
|
| 1136 |
+
currentData[rowIndex][column] = convertedValue;
|
| 1137 |
+
});
|
| 1138 |
+
|
| 1139 |
+
renderDataPreview();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1140 |
}
|
| 1141 |
|
| 1142 |
+
// Show add column modal
|
| 1143 |
+
function showAddColumnModal() {
|
| 1144 |
+
if (currentData.length === 0) {
|
| 1145 |
+
alert('Please import data first');
|
| 1146 |
+
return;
|
| 1147 |
+
}
|
| 1148 |
|
| 1149 |
+
newColumnName.value = '';
|
| 1150 |
+
columnFormula.value = '';
|
| 1151 |
+
columnType.value = 'number';
|
| 1152 |
+
addColumnModal.classList.remove('hidden');
|
| 1153 |
+
}
|
| 1154 |
+
|
| 1155 |
+
// Add calculated column
|
| 1156 |
+
function addCalculatedColumn() {
|
| 1157 |
+
const name = newColumnName.value.trim();
|
| 1158 |
+
const formula = columnFormula.value.trim();
|
| 1159 |
+
const type = columnType.value;
|
| 1160 |
+
|
| 1161 |
+
if (!name) {
|
| 1162 |
+
alert('Please enter a column name');
|
| 1163 |
+
return;
|
| 1164 |
+
}
|
| 1165 |
+
|
| 1166 |
+
if (Object.keys(currentData[0]).includes(name)) {
|
| 1167 |
+
alert('A column with this name already exists');
|
| 1168 |
+
return;
|
| 1169 |
+
}
|
| 1170 |
+
|
| 1171 |
+
// In a real app, we would parse the formula and calculate values
|
| 1172 |
+
// For demo, we'll just add a column with sample values
|
| 1173 |
+
currentData.forEach((row, index) => {
|
| 1174 |
+
// Simple example formula: sales * 0.2 (20% of sales)
|
| 1175 |
+
let value;
|
| 1176 |
+
if (formula.toLowerCase().includes('sales')) {
|
| 1177 |
+
value = row.sales * 0.2;
|
| 1178 |
+
} else {
|
| 1179 |
+
// Default to row index if no recognizable formula
|
| 1180 |
+
value = index + 1;
|
| 1181 |
}
|
| 1182 |
|
| 1183 |
+
// Convert to appropriate type
|
| 1184 |
+
if (type === 'number') {
|
| 1185 |
+
row[name] = parseFloat(value) || 0;
|
| 1186 |
+
} else if (type === 'text') {
|
| 1187 |
+
row[name] = value.toString();
|
| 1188 |
+
} else if (type === 'date') {
|
| 1189 |
+
row[name] = new Date();
|
| 1190 |
+
} else if (type === 'boolean') {
|
| 1191 |
+
row[name] = Math.random() > 0.5;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1192 |
}
|
| 1193 |
+
});
|
| 1194 |
+
|
| 1195 |
+
addColumnModal.classList.add('hidden');
|
| 1196 |
+
renderDataPreview();
|
| 1197 |
+
updateAllVisualizations();
|
| 1198 |
+
}
|
| 1199 |
+
|
| 1200 |
+
// Show filter modal
|
| 1201 |
+
function showFilterModal() {
|
| 1202 |
+
if (currentData.length === 0) {
|
| 1203 |
+
alert('Please import data first');
|
| 1204 |
+
return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1205 |
}
|
| 1206 |
+
|
| 1207 |
+
// Populate column select
|
| 1208 |
+
filterColumn.innerHTML = '';
|
| 1209 |
+
const headers = Object.keys(currentData[0]);
|
| 1210 |
+
headers.forEach(header => {
|
| 1211 |
+
const option = document.createElement('option');
|
| 1212 |
+
option.value = header;
|
| 1213 |
+
option.textContent = header;
|
| 1214 |
+
filterColumn.appendChild(option);
|
| 1215 |
+
});
|
| 1216 |
+
|
| 1217 |
+
filterModal.classList.remove('hidden');
|
| 1218 |
}
|
| 1219 |
|
| 1220 |
+
// Apply data filter
|
| 1221 |
+
function applyDataFilter() {
|
| 1222 |
+
const column = filterColumn.value;
|
| 1223 |
+
const condition = filterCondition.value;
|
| 1224 |
+
const value = filterValue.value;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1225 |
|
| 1226 |
+
if (condition !== 'empty' && condition !== 'not-empty' && !value) {
|
| 1227 |
+
alert('Please enter a filter value');
|
| 1228 |
+
return;
|
| 1229 |
+
}
|
|
|
|
| 1230 |
|
| 1231 |
+
// In a real app, we would apply the filter to the data
|
| 1232 |
+
// For demo, we'll just show a message
|
| 1233 |
+
alert(`Filter applied: ${column} ${condition} ${value}`);
|
|
|
|
|
|
|
| 1234 |
|
| 1235 |
+
filterModal.classList.add('hidden');
|
| 1236 |
+
}
|
| 1237 |
+
|
| 1238 |
+
// Perform data cleaning action
|
| 1239 |
+
function performDataCleaning(action) {
|
| 1240 |
+
if (currentData.length === 0) {
|
| 1241 |
+
alert('Please import data first');
|
| 1242 |
+
return;
|
| 1243 |
+
}
|
| 1244 |
|
| 1245 |
+
switch(action) {
|
| 1246 |
+
case 'remove-duplicates':
|
| 1247 |
+
// In a real app, we would actually remove duplicates
|
| 1248 |
+
const uniqueData = [];
|
| 1249 |
+
const seen = new Set();
|
| 1250 |
+
|
| 1251 |
+
currentData.forEach(row => {
|
| 1252 |
+
const key = JSON.stringify(row);
|
| 1253 |
+
if (!seen.has(key)) {
|
| 1254 |
+
seen.add(key);
|
| 1255 |
+
uniqueData.push(row);
|
| 1256 |
+
}
|
| 1257 |
+
});
|
| 1258 |
+
|
| 1259 |
+
currentData = uniqueData;
|
| 1260 |
+
alert(`${currentData.length - uniqueData.length} duplicate rows removed!`);
|
| 1261 |
+
break;
|
| 1262 |
+
case 'fill-missing':
|
| 1263 |
+
// In a real app, we would fill missing values
|
| 1264 |
+
alert('Missing values filled with defaults!');
|
| 1265 |
+
break;
|
| 1266 |
+
default:
|
| 1267 |
+
alert('Unknown action');
|
| 1268 |
}
|
| 1269 |
+
|
| 1270 |
+
// Refresh the preview
|
| 1271 |
+
renderDataPreview();
|
| 1272 |
+
updateAllVisualizations();
|
| 1273 |
}
|
| 1274 |
|
| 1275 |
+
// Add visualization to grid
|
| 1276 |
+
function addVisualizationToGrid() {
|
| 1277 |
+
if (currentData.length === 0) {
|
| 1278 |
+
alert('Please import data first');
|
| 1279 |
+
return;
|
| 1280 |
+
}
|
|
|
|
| 1281 |
|
| 1282 |
+
const vizId = `viz-${nextVizId++}`;
|
| 1283 |
+
const vizElement = document.createElement('div');
|
| 1284 |
+
vizElement.className = 'grid-stack-item';
|
| 1285 |
+
vizElement.dataset.id = vizId;
|
| 1286 |
+
vizElement.dataset.gsX = '0';
|
| 1287 |
+
vizElement.dataset.gsY = '0';
|
| 1288 |
+
vizElement.dataset.gsWidth = '4';
|
| 1289 |
+
vizElement.dataset.gsHeight = '3';
|
| 1290 |
|
| 1291 |
+
vizElement.innerHTML = `
|
| 1292 |
+
<div class="grid-stack-item-content">
|
| 1293 |
+
<div class="flex justify-between items-center mb-2">
|
| 1294 |
+
<h4 class="font-medium">New ${currentVizType} Chart</h4>
|
| 1295 |
+
<div class="flex space-x-1">
|
| 1296 |
+
<button class="viz-settings-btn p-1 text-gray-500 hover:text-blue-500" data-viz="${vizId}">
|
| 1297 |
+
<i class="fas fa-cog"></i>
|
| 1298 |
+
</button>
|
| 1299 |
+
<button class="viz-remove-btn p-1 text-gray-500 hover:text-red-500" data-viz="${vizId}">
|
| 1300 |
+
<i class="fas fa-times"></i>
|
| 1301 |
+
</button>
|
| 1302 |
+
</div>
|
| 1303 |
+
</div>
|
| 1304 |
+
<div class="chart-container relative">
|
| 1305 |
+
<canvas id="${vizId}-canvas"></canvas>
|
| 1306 |
+
</div>
|
| 1307 |
+
<div class="mt-2 text-xs text-gray-500">
|
| 1308 |
+
<span class="text-blue-500">${currentVizType}</span> chart
|
| 1309 |
+
</div>
|
| 1310 |
+
</div>
|
| 1311 |
+
`;
|
| 1312 |
|
| 1313 |
+
gridContainer.appendChild(vizElement);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1314 |
|
| 1315 |
+
// Initialize the chart
|
| 1316 |
+
const chartElement = document.getElementById(`${vizId}-canvas`);
|
| 1317 |
+
const chart = createChart(chartElement, currentVizType);
|
|
|
|
| 1318 |
|
| 1319 |
+
currentCharts.push({
|
| 1320 |
+
id: vizId,
|
| 1321 |
+
type: currentVizType,
|
| 1322 |
+
element: vizElement,
|
| 1323 |
+
chart: chart
|
| 1324 |
+
});
|
| 1325 |
|
| 1326 |
+
// Make the grid item draggable/resizable (in a real app, we'd use GridStack library)
|
| 1327 |
+
vizElement.addEventListener('mousedown', function(e) {
|
| 1328 |
+
if (e.target.classList.contains('viz-remove-btn')) {
|
| 1329 |
+
removeVisualization(e.target.dataset.viz);
|
| 1330 |
+
} else if (e.target.classList.contains('viz-settings-btn')) {
|
| 1331 |
+
configureVisualization(e.target.dataset.viz);
|
|
|
|
| 1332 |
}
|
| 1333 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1334 |
|
| 1335 |
+
// Initialize grid (simplified for this demo)
|
| 1336 |
+
initGridStack();
|
| 1337 |
}
|
| 1338 |
|
| 1339 |
+
// Create chart
|
| 1340 |
+
function createChart(canvasElement, type) {
|
| 1341 |
+
const headers = Object.keys(currentData[0]);
|
| 1342 |
+
const numericHeaders = headers.filter(h => typeof currentData[0][h] === 'number');
|
| 1343 |
+
const categoricalHeaders = headers.filter(h => typeof currentData[0][h] === 'string');
|
|
|
|
| 1344 |
|
| 1345 |
+
let xAxis = categoricalHeaders[0] || headers[0];
|
| 1346 |
+
let yAxis = numericHeaders[0] || headers[1] || headers[0];
|
| 1347 |
|
| 1348 |
+
if (type === 'pie') {
|
| 1349 |
+
yAxis = numericHeaders[0] || headers[0];
|
| 1350 |
+
} else if (type === 'scatter') {
|
| 1351 |
+
xAxis = numericHeaders[0] || headers[0];
|
| 1352 |
+
yAxis = numericHeaders[1] || headers[1] || headers[0];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1353 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1354 |
|
| 1355 |
+
const labels = currentData.map(item => item[xAxis]);
|
| 1356 |
+
const data = currentData.map(item => item[yAxis]);
|
|
|
|
|
|
|
|
|
|
| 1357 |
|
| 1358 |
+
const backgroundColors = getChartColors(data.length, 'default');
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1359 |
|
| 1360 |
+
const config = {
|
| 1361 |
+
type: type === 'pie' ? 'pie' :
|
| 1362 |
+
type === 'bar' ? 'bar' :
|
| 1363 |
+
type === 'line' ? 'line' :
|
| 1364 |
+
type === 'scatter' ? 'scatter' : 'bar',
|
| 1365 |
+
data: {
|
| 1366 |
+
labels: labels,
|
| 1367 |
+
datasets: [{
|
| 1368 |
+
label: yAxis,
|
| 1369 |
+
data: data,
|
| 1370 |
+
backgroundColor: backgroundColors,
|
| 1371 |
+
borderColor: type === 'pie' ? backgroundColors : '#3b82f6',
|
| 1372 |
+
borderWidth: 1
|
| 1373 |
+
}]
|
| 1374 |
+
},
|
| 1375 |
+
options: {
|
| 1376 |
+
responsive: true,
|
| 1377 |
+
maintainAspectRatio: false,
|
| 1378 |
+
plugins: {
|
| 1379 |
+
title: {
|
| 1380 |
+
display: true,
|
| 1381 |
+
text: `${type} Chart: ${xAxis} vs ${yAxis}`,
|
| 1382 |
+
font: {
|
| 1383 |
+
size: 14
|
| 1384 |
+
}
|
| 1385 |
+
},
|
| 1386 |
+
legend: {
|
| 1387 |
+
position: type === 'pie' ? 'right' : 'top',
|
| 1388 |
+
}
|
| 1389 |
+
},
|
| 1390 |
+
scales: type === 'pie' ? {} : {
|
| 1391 |
+
x: {
|
| 1392 |
+
grid: {
|
| 1393 |
+
color: isDarkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'
|
| 1394 |
+
}
|
| 1395 |
+
},
|
| 1396 |
+
y: {
|
| 1397 |
+
grid: {
|
| 1398 |
+
color: isDarkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'
|
| 1399 |
+
}
|
| 1400 |
+
}
|
| 1401 |
+
}
|
| 1402 |
+
}
|
| 1403 |
+
};
|
| 1404 |
|
| 1405 |
+
return new Chart(canvasElement, config);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1406 |
}
|
| 1407 |
|
| 1408 |
+
// Get chart colors based on scheme
|
| 1409 |
+
function getChartColors(count, scheme) {
|
|
|
|
|
|
|
| 1410 |
const schemes = {
|
| 1411 |
default: ['#3b82f6', '#ef4444', '#10b981', '#f59e0b', '#8b5cf6'],
|
| 1412 |
rainbow: ['#ff0000', '#ff7f00', '#ffff00', '#00ff00', '#0000ff', '#4b0082', '#9400d3'],
|
|
|
|
| 1417 |
};
|
| 1418 |
|
| 1419 |
const colors = schemes[scheme] || schemes.default;
|
| 1420 |
+
const result = [];
|
| 1421 |
+
|
| 1422 |
+
for (let i = 0; i < count; i++) {
|
| 1423 |
+
result.push(colors[i % colors.length]);
|
| 1424 |
+
}
|
| 1425 |
+
|
| 1426 |
+
return result;
|
| 1427 |
+
}
|
| 1428 |
+
|
| 1429 |
+
// Initialize grid stack (simplified for this demo)
|
| 1430 |
+
function initGridStack() {
|
| 1431 |
+
// In a real app, we would use GridStack library for proper grid functionality
|
| 1432 |
+
const items = gridContainer.querySelectorAll('.grid-stack-item');
|
| 1433 |
+
|
| 1434 |
+
items.forEach(item => {
|
| 1435 |
+
item.style.position = 'absolute';
|
| 1436 |
+
item.style.left = `${item.dataset.gsX * 25}%`;
|
| 1437 |
+
item.style.top = `${item.dataset.gsY * 100}px`;
|
| 1438 |
+
item.style.width = `${item.dataset.gsWidth * 25}%`;
|
| 1439 |
+
item.style.height = `${item.dataset.gsHeight * 100}px`;
|
| 1440 |
+
|
| 1441 |
+
// Make draggable (simplified)
|
| 1442 |
+
item.draggable = true;
|
| 1443 |
+
item.addEventListener('dragstart', handleDragStart);
|
| 1444 |
+
item.addEventListener('dragend', handleDragEnd);
|
| 1445 |
+
});
|
| 1446 |
+
|
| 1447 |
+
gridContainer.addEventListener('dragover', handleDragOver);
|
| 1448 |
+
gridContainer.addEventListener('drop', handleDropItem);
|
| 1449 |
+
}
|
| 1450 |
+
|
| 1451 |
+
// Drag and drop handlers for grid items (simplified)
|
| 1452 |
+
function handleDragStart(e) {
|
| 1453 |
+
e.dataTransfer.setData('text/plain', e.target.dataset.id);
|
| 1454 |
+
e.target.style.opacity = '0.5';
|
| 1455 |
+
}
|
| 1456 |
+
|
| 1457 |
+
function handleDragEnd(e) {
|
| 1458 |
+
e.target.style.opacity = '1';
|
| 1459 |
+
}
|
| 1460 |
+
|
| 1461 |
+
function handleDragOver(e) {
|
| 1462 |
+
e.preventDefault();
|
| 1463 |
+
}
|
| 1464 |
+
|
| 1465 |
+
function handleDropItem(e) {
|
| 1466 |
+
e.preventDefault();
|
| 1467 |
+
const id = e.dataTransfer.getData('text/plain');
|
| 1468 |
+
const item = document.querySelector(`[data-id="${id}"]`);
|
| 1469 |
+
|
| 1470 |
+
if (item && e.target === gridContainer) {
|
| 1471 |
+
const rect = gridContainer.getBoundingClientRect();
|
| 1472 |
+
const x = e.clientX - rect.left;
|
| 1473 |
+
const y = e.clientY - rect.top;
|
| 1474 |
+
|
| 1475 |
+
// Calculate new position (simplified)
|
| 1476 |
+
const col = Math.floor(x / (rect.width / 4));
|
| 1477 |
+
const row = Math.floor(y / 100);
|
| 1478 |
+
|
| 1479 |
+
item.style.left = `${col * 25}%`;
|
| 1480 |
+
item.style.top = `${row * 100}px`;
|
| 1481 |
+
item.dataset.gsX = col;
|
| 1482 |
+
item.dataset.gsY = row;
|
| 1483 |
+
}
|
| 1484 |
+
}
|
| 1485 |
+
|
| 1486 |
+
// Remove visualization
|
| 1487 |
+
function removeVisualization(id) {
|
| 1488 |
+
if (confirm('Are you sure you want to remove this visualization?')) {
|
| 1489 |
+
const index = currentCharts.findIndex(chart => chart.id === id);
|
| 1490 |
+
if (index >= 0) {
|
| 1491 |
+
currentCharts[index].chart.destroy();
|
| 1492 |
+
currentCharts[index].element.remove();
|
| 1493 |
+
currentCharts.splice(index, 1);
|
| 1494 |
+
}
|
| 1495 |
+
}
|
| 1496 |
+
}
|
| 1497 |
+
|
| 1498 |
+
// Configure visualization
|
| 1499 |
+
function configureVisualization(id) {
|
| 1500 |
+
const chartConfig = currentCharts.find(chart => chart.id === id);
|
| 1501 |
+
if (chartConfig) {
|
| 1502 |
+
alert(`In a real app, this would show configuration options for ${chartConfig.type} chart`);
|
| 1503 |
+
}
|
| 1504 |
+
}
|
| 1505 |
+
|
| 1506 |
+
// Update all visualizations when data changes
|
| 1507 |
+
function updateAllVisualizations() {
|
| 1508 |
+
currentCharts.forEach(chartConfig => {
|
| 1509 |
+
updateVisualization(chartConfig);
|
| 1510 |
+
});
|
| 1511 |
+
}
|
| 1512 |
+
|
| 1513 |
+
// Update a specific visualization
|
| 1514 |
+
function updateVisualization(chartConfig) {
|
| 1515 |
+
const headers = Object.keys(currentData[0]);
|
| 1516 |
+
const numericHeaders = headers.filter(h => typeof currentData[0][h] === 'number');
|
| 1517 |
+
const categoricalHeaders = headers.filter(h => typeof currentData[0][h] === 'string');
|
| 1518 |
+
|
| 1519 |
+
let xAxis = categoricalHeaders[0] || headers[0];
|
| 1520 |
+
let yAxis = numericHeaders[0] || headers[1] || headers[0];
|
| 1521 |
+
|
| 1522 |
+
if (chartConfig.type === 'pie') {
|
| 1523 |
+
yAxis = numericHeaders[0] || headers[0];
|
| 1524 |
+
} else if (chartConfig.type === 'scatter') {
|
| 1525 |
+
xAxis = numericHeaders[0] || headers[0];
|
| 1526 |
+
yAxis = numericHeaders[1] || headers[1] || headers[0];
|
| 1527 |
+
}
|
| 1528 |
+
|
| 1529 |
+
const labels = currentData.map(item => item[xAxis]);
|
| 1530 |
+
const data = currentData.map(item => item[yAxis]);
|
| 1531 |
+
|
| 1532 |
+
chartConfig.chart.data.labels = labels;
|
| 1533 |
+
chartConfig.chart.data.datasets[0].data = data;
|
| 1534 |
+
chartConfig.chart.data.datasets[0].label = yAxis;
|
| 1535 |
+
chartConfig.chart.options.plugins.title.text = `${chartConfig.type} Chart: ${xAxis} vs ${yAxis}`;
|
| 1536 |
+
chartConfig.chart.update();
|
| 1537 |
+
}
|
| 1538 |
+
|
| 1539 |
+
// Reset visualizations
|
| 1540 |
+
function resetVisualizations() {
|
| 1541 |
+
gridContainer.innerHTML = '';
|
| 1542 |
+
currentCharts.forEach(chart => chart.chart.destroy());
|
| 1543 |
+
currentCharts = [];
|
| 1544 |
+
nextVizId = 1;
|
| 1545 |
}
|
| 1546 |
|
| 1547 |
// Generate AI recommendation
|
|
|
|
| 1556 |
const headers = Object.keys(currentData[0]);
|
| 1557 |
const numericHeaders = headers.filter(h => typeof currentData[0][h] === 'number');
|
| 1558 |
const categoricalHeaders = headers.filter(h => typeof currentData[0][h] === 'string');
|
| 1559 |
+
const dateHeaders = headers.filter(h => !isNaN(Date.parse(currentData[0][h])));
|
| 1560 |
|
| 1561 |
let recommendation = '';
|
| 1562 |
|
| 1563 |
+
if (dateHeaders.length > 0 && numericHeaders.length > 0) {
|
| 1564 |
+
recommendation = `A line chart showing ${numericHeaders[0]} over time would effectively visualize trends in your data.`;
|
| 1565 |
+
currentVizType = 'line';
|
| 1566 |
+
}
|
| 1567 |
+
else if (numericHeaders.length >= 2 && categoricalHeaders.length >= 1) {
|
| 1568 |
recommendation = `A grouped bar chart comparing ${numericHeaders[0]} and ${numericHeaders[1]} across different ${categoricalHeaders[0]} categories would effectively show the relationships in your data.`;
|
| 1569 |
+
currentVizType = 'bar';
|
| 1570 |
+
}
|
| 1571 |
+
else if (numericHeaders.length >= 1 && categoricalHeaders.length >= 1) {
|
| 1572 |
recommendation = `A pie chart showing the distribution of ${numericHeaders[0]} by ${categoricalHeaders[0]} would be a good choice to visualize proportions.`;
|
| 1573 |
+
currentVizType = 'pie';
|
| 1574 |
+
}
|
| 1575 |
+
else if (numericHeaders.length >= 2) {
|
| 1576 |
recommendation = `A scatter plot with ${numericHeaders[0]} on the X-axis and ${numericHeaders[1]} on the Y-axis would help identify correlations.`;
|
| 1577 |
+
currentVizType = 'scatter';
|
| 1578 |
+
}
|
| 1579 |
+
else {
|
| 1580 |
recommendation = `Based on your data structure, a simple table might be the most effective way to present this information.`;
|
| 1581 |
}
|
| 1582 |
|
|
|
|
| 1586 |
|
| 1587 |
// Apply AI recommendation
|
| 1588 |
function applyAiRecommendation() {
|
| 1589 |
+
addVisualizationToGrid();
|
| 1590 |
+
aiRecommendationSection.classList.add('hidden');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1591 |
}
|
| 1592 |
|
| 1593 |
+
// Load template
|
| 1594 |
+
function loadTemplate(template) {
|
| 1595 |
+
if (template === 'sales') {
|
| 1596 |
+
// In a real app, this would configure sales-specific visualizations
|
| 1597 |
+
resetVisualizations();
|
| 1598 |
+
currentVizType = 'bar';
|
| 1599 |
+
addVisualizationToGrid();
|
| 1600 |
+
currentVizType = 'line';
|
| 1601 |
+
addVisualizationToGrid();
|
| 1602 |
+
alert('Sales dashboard template loaded with sample visualizations');
|
| 1603 |
+
}
|
| 1604 |
+
else if (template === 'customer') {
|
| 1605 |
+
resetVisualizations();
|
| 1606 |
+
currentVizType = 'pie';
|
| 1607 |
+
addVisualizationToGrid();
|
| 1608 |
+
currentVizType = 'bar';
|
| 1609 |
+
addVisualizationToGrid();
|
| 1610 |
+
alert('Customer analytics template loaded with sample visualizations');
|
| 1611 |
+
}
|
| 1612 |
+
else if (template === 'process') {
|
| 1613 |
+
resetVisualizations();
|
| 1614 |
+
currentVizType = 'flowchart';
|
| 1615 |
+
addVisualizationToGrid();
|
| 1616 |
+
alert('Process flow template loaded with sample visualization');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1617 |
}
|
| 1618 |
}
|
| 1619 |
|
| 1620 |
+
// Export dashboard
|
| 1621 |
+
function exportDashboard(format, quality, size) {
|
| 1622 |
+
// In a real app, we would generate the export file
|
| 1623 |
+
alert(`Exporting dashboard as ${format} (${quality}, ${size})`);
|
| 1624 |
+
}
|
| 1625 |
+
|
| 1626 |
+
// Save dashboard
|
| 1627 |
+
function saveDashboard() {
|
| 1628 |
+
const dashboardConfig = {
|
| 1629 |
+
visualizations: currentCharts.map(chart => ({
|
| 1630 |
+
id: chart.id,
|
| 1631 |
+
type: chart
|
| 1632 |
</html>
|
prompts.txt
CHANGED
|
@@ -1 +1,2 @@
|
|
| 1 |
-
Hello, I would like to create a data visualization dashboard web app. Here are the key functionalities I need: Allow users to import data from CSV or Excel files. After importing, allow users to select what type of visualization they want to create: Charts (bar, line, pie, scatter, etc.) Diagrams (flowcharts, organizational charts, etc.) Mind Maps Enable drag-and-drop interface for building and customizing visualizations. Provide basic data cleaning options (e.g., remove duplicates, fill missing values). Allow users to filter, sort, and group data before visualization. Enable dynamic updating: if the data changes, the visualization updates automatically. Allow users to customize the visuals (colors, labels, titles, sizes, etc.). Support saving and exporting visualizations (as images or PDFs). Provide dashboard templates for faster setup. Offer an AI assistant suggestion feature: recommend the best type of visualization based on the dataset. Support multi-language UI (optional, but nice to have). The dashboard should have a clean, responsive design that works on desktop and mobile.
|
|
|
|
|
|
| 1 |
+
Hello, I would like to create a data visualization dashboard web app. Here are the key functionalities I need: Allow users to import data from CSV or Excel files. After importing, allow users to select what type of visualization they want to create: Charts (bar, line, pie, scatter, etc.) Diagrams (flowcharts, organizational charts, etc.) Mind Maps Enable drag-and-drop interface for building and customizing visualizations. Provide basic data cleaning options (e.g., remove duplicates, fill missing values). Allow users to filter, sort, and group data before visualization. Enable dynamic updating: if the data changes, the visualization updates automatically. Allow users to customize the visuals (colors, labels, titles, sizes, etc.). Support saving and exporting visualizations (as images or PDFs). Provide dashboard templates for faster setup. Offer an AI assistant suggestion feature: recommend the best type of visualization based on the dataset. Support multi-language UI (optional, but nice to have). The dashboard should have a clean, responsive design that works on desktop and mobile.
|
| 2 |
+
Hello, I would like to create a fully functional data visualization dashboard, keeping a clean and modern UI like the attached reference. Here’s exactly what I need: Core Functionalities: Data Import: Drag-and-drop + browse to upload files. Support CSV and Excel (XLS, XLSX) formats. Validate uploaded file (format errors, missing values). Data Preview: Show imported data in a clean table. Allow basic transformations: Edit cells manually. Filter columns (e.g., hide/show). Sort and search. Add simple calculated columns (optional). Transform data (change column types: number, text, date). Visualization Workspace: Let users select: Chart type (Bar, Line, Pie, Scatter, Flowchart, Mind Map). X-axis and Y-axis fields dynamically. Color scheme (default options, light/dark themes). Chart title and labels. When user clicks "Update Visualization," render the chart instantly based on selected settings. Dashboard Templates: Offer ready-made templates like: Sales Dashboard Customer Analytics Process Flow Users can load a template to auto-configure charts based on typical datasets. Save and Export: Allow saving dashboards in the browser (local storage first, option for cloud later). Export visualizations as PNG, PDF, or CSV. Enhancements to Make It Great: AI Suggestions Panel: Recommend the best chart type based on the dataset (e.g., large numbers -> bar chart, time series -> line chart). Multi-visualization Dashboard: Let users create multiple charts and arrange them freely (drag-and-drop layout). Live Updates: If data is edited, visualization updates live without needing to press "update." Responsive Design: Fully optimized for desktop, tablet, and mobile. Help Tooltip System: Small "?" icons near each feature for quick help pop-ups. Recent Files and Auto-save: Show a list of recently uploaded files. Auto-save the latest dashboard state locally. Important Notes: Prioritize fast performance even with large files (optimize loading and rendering). Ensure the dashboard is user-friendly and no-code — users should not need technical skills to use it.
|