anzuo commited on
Commit
79eb658
·
verified ·
1 Parent(s): 241b051

Add 3 files

Browse files
Files changed (3) hide show
  1. README.md +7 -5
  2. index.html +1183 -19
  3. prompts.txt +1 -0
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Deepsite
3
- emoji: 🏃
4
- colorFrom: gray
5
- colorTo: pink
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: deepsite
3
+ emoji: 🐳
4
+ colorFrom: red
5
+ colorTo: green
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,1183 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Large CSV Analyzer</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/papaparse@5.3.0/papaparse.min.js"></script>
10
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
11
+ <style>
12
+ .dropzone {
13
+ border: 2px dashed #ccc;
14
+ transition: all 0.3s ease;
15
+ }
16
+ .dropzone.active {
17
+ border-color: #4f46e5;
18
+ background-color: #f0f7ff;
19
+ }
20
+ .progress-bar {
21
+ transition: width 0.3s ease;
22
+ }
23
+ .column-selector:hover {
24
+ background-color: #f3f4f6;
25
+ }
26
+ .column-type-selector {
27
+ max-width: 80px;
28
+ background-color: white;
29
+ }
30
+ .column-type-selector:focus {
31
+ outline: none;
32
+ border-color: #4f46e5;
33
+ }
34
+ .column-selected {
35
+ background-color: #e0e7ff;
36
+ border-left: 3px solid #4f46e5;
37
+ }
38
+ .data-table-container {
39
+ max-height: 500px;
40
+ overflow-y: auto;
41
+ }
42
+ .chart-container {
43
+ height: 400px;
44
+ }
45
+
46
+ /* Custom scrollbar */
47
+ ::-webkit-scrollbar {
48
+ width: 8px;
49
+ height: 8px;
50
+ }
51
+ ::-webkit-scrollbar-track {
52
+ background: #f1f1f1;
53
+ }
54
+ ::-webkit-scrollbar-thumb {
55
+ background: #c7d2fe;
56
+ border-radius: 4px;
57
+ }
58
+ ::-webkit-scrollbar-thumb:hover {
59
+ background: #a5b4fc;
60
+ }
61
+
62
+ /* Table styling */
63
+ .table-wrapper {
64
+ overflow-x: auto;
65
+ width: 100%;
66
+ }
67
+ .data-table {
68
+ min-width: 100%;
69
+ width: auto;
70
+ }
71
+ .data-table th, .data-table td {
72
+ white-space: nowrap;
73
+ padding: 0.75rem 1rem;
74
+ text-align: left;
75
+ border-bottom: 1px solid #e5e7eb;
76
+ }
77
+ .data-table th {
78
+ position: sticky;
79
+ top: 0;
80
+ background-color: #f9fafb;
81
+ z-index: 10;
82
+ }
83
+ .data-table tr:hover {
84
+ background-color: #f3f4f6;
85
+ }
86
+ .row-number {
87
+ color: #6b7280;
88
+ font-weight: 500;
89
+ }
90
+ .pagination-btn {
91
+ min-width: 2.5rem;
92
+ }
93
+ .pagination-btn.active {
94
+ background-color: #4f46e5;
95
+ color: white;
96
+ }
97
+ </style>
98
+ </head>
99
+ <body class="bg-gray-50 min-h-screen">
100
+ <div class="container mx-auto px-4 py-8">
101
+ <div class="text-center mb-8">
102
+ <h1 class="text-3xl font-bold text-indigo-700 mb-2">Large CSV Analyzer</h1>
103
+ <p class="text-gray-600">Upload CSV files up to 5GB, preview data, and generate insightful visualizations</p>
104
+ </div>
105
+
106
+ <!-- File Upload Section -->
107
+ <div class="bg-white rounded-lg shadow-md p-6 mb-8">
108
+ <div id="upload-container" class="mb-6">
109
+ <div id="dropzone" class="dropzone rounded-lg p-12 text-center cursor-pointer">
110
+ <div class="flex flex-col items-center justify-center">
111
+ <i class="fas fa-cloud-upload-alt text-4xl text-indigo-500 mb-4"></i>
112
+ <h3 class="text-xl font-semibold text-gray-700 mb-2">Drag & Drop your CSV file here</h3>
113
+ <p class="text-gray-500 mb-4">or</p>
114
+ <label for="file-input" class="bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-2 px-6 rounded-md cursor-pointer transition duration-200">
115
+ Browse Files
116
+ </label>
117
+ <input id="file-input" type="file" accept=".csv" class="hidden">
118
+ </div>
119
+ </div>
120
+ <div class="mt-4 text-sm text-gray-500">
121
+ <p>Supported file types: .csv (max 5GB)</p>
122
+ </div>
123
+ </div>
124
+
125
+ <!-- Upload Progress -->
126
+ <div id="upload-progress" class="hidden">
127
+ <div class="flex justify-between mb-1">
128
+ <span class="text-sm font-medium text-indigo-700" id="filename-display"></span>
129
+ <span class="text-sm font-medium text-indigo-700" id="progress-percentage">0%</span>
130
+ </div>
131
+ <div class="w-full bg-gray-200 rounded-full h-2.5">
132
+ <div id="progress-bar" class="progress-bar bg-indigo-600 h-2.5 rounded-full" style="width: 0%"></div>
133
+ </div>
134
+ <div class="flex justify-between mt-2 text-sm text-gray-500">
135
+ <span id="uploaded-size">0 MB</span>
136
+ <span id="total-size">0 MB</span>
137
+ </div>
138
+ <div class="mt-4 flex justify-center">
139
+ <button id="cancel-upload" class="text-red-600 hover:text-red-800 font-medium flex items-center">
140
+ <i class="fas fa-times-circle mr-2"></i> Cancel Upload
141
+ </button>
142
+ </div>
143
+ </div>
144
+ </div>
145
+
146
+ <!-- Data Preview Section -->
147
+ <div id="data-preview" class="hidden bg-white rounded-lg shadow-md p-6 mb-8">
148
+ <div class="flex justify-between items-center mb-6">
149
+ <h2 class="text-xl font-semibold text-gray-800">Data Preview</h2>
150
+ <div class="flex gap-2">
151
+ <button id="reset-analysis" class="text-indigo-600 hover:text-indigo-800 font-medium flex items-center">
152
+ <i class="fas fa-redo-alt mr-2"></i> Reset Analysis
153
+ </button>
154
+ <button id="load-full-data" class="bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-2 px-4 rounded-md">
155
+ <i class="fas fa-database mr-2"></i> Load Full Dataset
156
+ </button>
157
+ </div>
158
+ </div>
159
+
160
+ <div class="flex flex-col md:flex-row gap-6">
161
+ <!-- Column Selector -->
162
+ <div class="w-full md:w-1/4">
163
+ <div class="bg-gray-50 rounded-lg p-4 border border-gray-200">
164
+ <h3 class="font-medium text-gray-700 mb-3">Select Columns</h3>
165
+ <div class="space-y-2 max-h-96 overflow-y-auto" id="column-list">
166
+ <!-- Columns will be added here dynamically -->
167
+ </div>
168
+ <div class="mt-4">
169
+ <button id="analyze-btn" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-2 px-4 rounded-md disabled:opacity-50 disabled:cursor-not-allowed" disabled>
170
+ <i class="fas fa-chart-bar mr-2"></i> Analyze Selected Columns
171
+ </button>
172
+ </div>
173
+ </div>
174
+ </div>
175
+
176
+ <!-- Data Table -->
177
+ <div class="w-full md:w-3/4">
178
+ <div class="table-wrapper">
179
+ <div class="data-table-container border border-gray-200 rounded-lg">
180
+ <table class="data-table">
181
+ <thead>
182
+ <tr id="table-header">
183
+ <th class="row-number">#</th>
184
+ <!-- Headers will be added here dynamically -->
185
+ </tr>
186
+ </thead>
187
+ <tbody id="table-body">
188
+ <!-- Data rows will be added here dynamically -->
189
+ </tbody>
190
+ </table>
191
+ </div>
192
+ </div>
193
+
194
+ <!-- Pagination Controls -->
195
+ <div class="flex flex-col sm:flex-row items-center justify-between mt-4">
196
+ <div class="text-sm text-gray-500 mb-2 sm:mb-0">
197
+ <span id="row-count-display">Showing rows 1-20 of 20</span>
198
+ </div>
199
+ <div class="flex items-center space-x-1" id="pagination-controls">
200
+ <button class="pagination-btn bg-white border border-gray-300 rounded-md px-3 py-1 text-sm font-medium text-gray-700 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed" id="first-page" disabled>
201
+ <i class="fas fa-angle-double-left"></i>
202
+ </button>
203
+ <button class="pagination-btn bg-white border border-gray-300 rounded-md px-3 py-1 text-sm font-medium text-gray-700 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed" id="prev-page" disabled>
204
+ <i class="fas fa-angle-left"></i>
205
+ </button>
206
+ <div class="flex space-x-1" id="page-numbers">
207
+ <!-- Page numbers will be added here dynamically -->
208
+ </div>
209
+ <button class="pagination-btn bg-white border border-gray-300 rounded-md px-3 py-1 text-sm font-medium text-gray-700 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed" id="next-page" disabled>
210
+ <i class="fas fa-angle-right"></i>
211
+ </button>
212
+ <button class="pagination-btn bg-white border border-gray-300 rounded-md px-3 py-1 text-sm font-medium text-gray-700 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed" id="last-page" disabled>
213
+ <i class="fas fa-angle-double-right"></i>
214
+ </button>
215
+ </div>
216
+ <div class="flex items-center mt-2 sm:mt-0">
217
+ <span class="text-sm text-gray-500 mr-2">Rows per page:</span>
218
+ <select id="rows-per-page" class="border border-gray-300 rounded-md px-2 py-1 text-sm">
219
+ <option value="20">20</option>
220
+ <option value="50">50</option>
221
+ <option value="100">100</option>
222
+ <option value="200">200</option>
223
+ </select>
224
+ </div>
225
+ </div>
226
+ </div>
227
+ </div>
228
+ </div>
229
+
230
+ <!-- Analysis Results Section -->
231
+ <div id="analysis-results" class="hidden bg-white rounded-lg shadow-md p-6 mb-8">
232
+ <h2 class="text-xl font-semibold text-gray-800 mb-6">Analysis Results</h2>
233
+
234
+ <!-- Statistics Summary -->
235
+ <div class="mb-8">
236
+ <h3 class="font-medium text-gray-700 mb-3">Summary Statistics</h3>
237
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4" id="stats-summary">
238
+ <!-- Statistics cards will be added here dynamically -->
239
+ </div>
240
+ </div>
241
+
242
+ <!-- Visualization -->
243
+ <div class="mb-6">
244
+ <h3 class="font-medium text-gray-700 mb-3">Visualization</h3>
245
+ <div class="flex flex-wrap gap-4 mb-4">
246
+ <select id="chart-type" class="border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500">
247
+ <option value="bar">Bar Chart</option>
248
+ <option value="line">Line Chart</option>
249
+ <option value="pie">Pie Chart</option>
250
+ <option value="scatter">Scatter Plot</option>
251
+ <option value="histogram">Histogram</option>
252
+ </select>
253
+ <button id="update-chart" class="bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-2 px-4 rounded-md">
254
+ Update Chart
255
+ </button>
256
+ <button id="export-chart" class="border border-indigo-600 text-indigo-600 hover:bg-indigo-50 font-medium py-2 px-4 rounded-md">
257
+ <i class="fas fa-download mr-2"></i> Export Image
258
+ </button>
259
+ </div>
260
+ <div class="chart-container bg-gray-50 rounded-lg p-4 border border-gray-200">
261
+ <canvas id="analysis-chart"></canvas>
262
+ </div>
263
+ </div>
264
+ </div>
265
+ </div>
266
+
267
+ <script>
268
+ // Global variables
269
+ let csvData = [];
270
+ let fullCsvData = [];
271
+ let headers = [];
272
+ let selectedColumns = [];
273
+ let columnTypes = {}; // Track column data types
274
+ let chart = null;
275
+ let fileReader = null;
276
+ let uploadAbortController = null;
277
+ const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB chunks for large files
278
+ let isFullDataLoaded = false;
279
+
280
+ // Pagination variables
281
+ let currentPage = 1;
282
+ let rowsPerPage = 20;
283
+ let totalPages = 1;
284
+ let visiblePages = 5; // Number of visible page buttons
285
+
286
+ // DOM elements
287
+ const dropzone = document.getElementById('dropzone');
288
+ const fileInput = document.getElementById('file-input');
289
+ const uploadContainer = document.getElementById('upload-container');
290
+ const uploadProgress = document.getElementById('upload-progress');
291
+ const progressBar = document.getElementById('progress-bar');
292
+ const progressPercentage = document.getElementById('progress-percentage');
293
+ const uploadedSize = document.getElementById('uploaded-size');
294
+ const totalSize = document.getElementById('total-size');
295
+ const filenameDisplay = document.getElementById('filename-display');
296
+ const cancelUpload = document.getElementById('cancel-upload');
297
+ const dataPreview = document.getElementById('data-preview');
298
+ const columnList = document.getElementById('column-list');
299
+ const tableHeader = document.getElementById('table-header');
300
+ const tableBody = document.getElementById('table-body');
301
+ const analyzeBtn = document.getElementById('analyze-btn');
302
+ const analysisResults = document.getElementById('analysis-results');
303
+ const statsSummary = document.getElementById('stats-summary');
304
+ const chartCanvas = document.getElementById('analysis-chart');
305
+ const chartTypeSelect = document.getElementById('chart-type');
306
+ const updateChartBtn = document.getElementById('update-chart');
307
+ const exportChartBtn = document.getElementById('export-chart');
308
+ const resetAnalysisBtn = document.getElementById('reset-analysis');
309
+ const loadFullDataBtn = document.getElementById('load-full-data');
310
+ const rowCountDisplay = document.getElementById('row-count-display');
311
+ const firstPageBtn = document.getElementById('first-page');
312
+ const prevPageBtn = document.getElementById('prev-page');
313
+ const nextPageBtn = document.getElementById('next-page');
314
+ const lastPageBtn = document.getElementById('last-page');
315
+ const pageNumbersContainer = document.getElementById('page-numbers');
316
+ const rowsPerPageSelect = document.getElementById('rows-per-page');
317
+
318
+ // Event listeners
319
+ dropzone.addEventListener('dragover', (e) => {
320
+ e.preventDefault();
321
+ dropzone.classList.add('active');
322
+ });
323
+
324
+ dropzone.addEventListener('dragleave', () => {
325
+ dropzone.classList.remove('active');
326
+ });
327
+
328
+ dropzone.addEventListener('drop', (e) => {
329
+ e.preventDefault();
330
+ dropzone.classList.remove('active');
331
+ if (e.dataTransfer.files.length) {
332
+ fileInput.files = e.dataTransfer.files;
333
+ handleFileUpload(e.dataTransfer.files[0]);
334
+ }
335
+ });
336
+
337
+ fileInput.addEventListener('change', () => {
338
+ if (fileInput.files.length) {
339
+ handleFileUpload(fileInput.files[0]);
340
+ }
341
+ });
342
+
343
+ cancelUpload.addEventListener('click', () => {
344
+ if (uploadAbortController) {
345
+ uploadAbortController.abort();
346
+ }
347
+ resetUploadUI();
348
+ });
349
+
350
+ analyzeBtn.addEventListener('click', analyzeSelectedColumns);
351
+ updateChartBtn.addEventListener('click', updateChart);
352
+ exportChartBtn.addEventListener('click', exportChart);
353
+ resetAnalysisBtn.addEventListener('click', resetAnalysis);
354
+ loadFullDataBtn.addEventListener('click', loadFullDataset);
355
+
356
+ // Pagination event listeners
357
+ firstPageBtn.addEventListener('click', () => goToPage(1));
358
+ prevPageBtn.addEventListener('click', () => goToPage(currentPage - 1));
359
+ nextPageBtn.addEventListener('click', () => goToPage(currentPage + 1));
360
+ lastPageBtn.addEventListener('click', () => goToPage(totalPages));
361
+ rowsPerPageSelect.addEventListener('change', (e) => {
362
+ rowsPerPage = parseInt(e.target.value);
363
+ currentPage = 1;
364
+ updatePagination();
365
+ renderTablePage();
366
+ });
367
+
368
+ // Functions
369
+ function handleFileUpload(file) {
370
+ if (!file.name.endsWith('.csv')) {
371
+ showError('Please upload a CSV file');
372
+ return;
373
+ }
374
+
375
+ // Reset state
376
+ isFullDataLoaded = false;
377
+ csvData = [];
378
+ fullCsvData = [];
379
+ headers = [];
380
+ selectedColumns = [];
381
+ currentPage = 1;
382
+ rowsPerPage = 20;
383
+
384
+ // Show upload progress UI
385
+ uploadContainer.classList.add('hidden');
386
+ uploadProgress.classList.remove('hidden');
387
+ filenameDisplay.textContent = file.name;
388
+ totalSize.textContent = formatFileSize(file.size);
389
+
390
+ // For files larger than 50MB, use chunked reading
391
+ if (file.size > 50 * 1024 * 1024) {
392
+ uploadLargeFile(file);
393
+ } else {
394
+ uploadSmallFile(file);
395
+ }
396
+ }
397
+
398
+ function uploadSmallFile(file) {
399
+ uploadAbortController = new AbortController();
400
+
401
+ // Simulate upload progress for demo purposes
402
+ let uploaded = 0;
403
+ const total = file.size;
404
+ const interval = setInterval(() => {
405
+ uploaded += Math.min(CHUNK_SIZE / 10, total - uploaded);
406
+ updateProgress(uploaded, total);
407
+
408
+ if (uploaded >= total) {
409
+ clearInterval(interval);
410
+ parseCSVFile(file, true);
411
+ }
412
+ }, 100);
413
+ }
414
+
415
+ function uploadLargeFile(file) {
416
+ uploadAbortController = new AbortController();
417
+
418
+ // In a real application, you would send chunks to the server
419
+ // This is a simulation for the UI
420
+ let uploaded = 0;
421
+ const total = file.size;
422
+ const chunks = Math.ceil(total / CHUNK_SIZE);
423
+
424
+ const uploadNextChunk = (chunkIndex) => {
425
+ if (uploadAbortController.signal.aborted) return;
426
+
427
+ const start = chunkIndex * CHUNK_SIZE;
428
+ const end = Math.min(start + CHUNK_SIZE, total);
429
+ const chunk = file.slice(start, end);
430
+
431
+ // Simulate chunk upload delay
432
+ setTimeout(() => {
433
+ uploaded = end;
434
+ updateProgress(uploaded, total);
435
+
436
+ if (chunkIndex < chunks - 1) {
437
+ uploadNextChunk(chunkIndex + 1);
438
+ } else {
439
+ parseCSVFile(file, true);
440
+ }
441
+ }, 300);
442
+ };
443
+
444
+ uploadNextChunk(0);
445
+ }
446
+
447
+ function convertValue(value, type) {
448
+ if (!value) return value;
449
+
450
+ switch(type) {
451
+ case 'number':
452
+ return parseFloat(value) || 0;
453
+ case 'date':
454
+ return new Date(value);
455
+ case 'boolean':
456
+ return value.toLowerCase() === 'true' || value === '1';
457
+ default:
458
+ return value.toString();
459
+ }
460
+ }
461
+
462
+ function parseCSVFile(file, isPreview = false) {
463
+ fileReader = new FileReader();
464
+
465
+ fileReader.onload = (e) => {
466
+ try {
467
+ const results = Papa.parse(e.target.result, {
468
+ header: true,
469
+ skipEmptyLines: true,
470
+ preview: isPreview ? 20 : null // Only parse first 20 rows for preview
471
+ });
472
+
473
+ if (results.errors.length > 0) {
474
+ showError('Error parsing CSV: ' + results.errors[0].message);
475
+ resetUploadUI();
476
+ return;
477
+ }
478
+
479
+ // Initialize column types as string by default
480
+ headers.forEach(header => {
481
+ columnTypes[header] = 'string';
482
+ });
483
+
484
+ if (isPreview) {
485
+ csvData = results.data;
486
+ fullCsvData = []; // Will be loaded later if needed
487
+ } else {
488
+ fullCsvData = results.data;
489
+ csvData = fullCsvData.slice(0, 20); // Update preview with full data
490
+ isFullDataLoaded = true;
491
+ loadFullDataBtn.classList.add('hidden');
492
+ }
493
+
494
+ // Add event listeners for type selectors after DOM is updated
495
+ setTimeout(() => {
496
+ document.querySelectorAll('.column-type-selector').forEach(select => {
497
+ select.addEventListener('change', (e) => {
498
+ const column = e.target.dataset.column;
499
+ columnTypes[column] = e.target.value;
500
+ renderTablePage();
501
+ });
502
+ });
503
+ }, 0);
504
+
505
+ headers = results.meta.fields;
506
+
507
+ if (csvData.length === 0) {
508
+ showError('CSV file is empty or could not be parsed');
509
+ resetUploadUI();
510
+ return;
511
+ }
512
+
513
+ displayDataPreview();
514
+ } catch (error) {
515
+ showError('Error parsing CSV: ' + error.message);
516
+ resetUploadUI();
517
+ }
518
+ };
519
+
520
+ fileReader.onerror = () => {
521
+ showError('Error reading file');
522
+ resetUploadUI();
523
+ };
524
+
525
+ fileReader.readAsText(file);
526
+ }
527
+
528
+ function loadFullDataset() {
529
+ if (fileInput.files.length === 0) return;
530
+
531
+ // Show loading state
532
+ loadFullDataBtn.disabled = true;
533
+ loadFullDataBtn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Loading...';
534
+
535
+ // Re-parse the file but this time without the preview limit
536
+ parseCSVFile(fileInput.files[0], false);
537
+ }
538
+
539
+ function displayDataPreview() {
540
+ // Show data preview section
541
+ uploadContainer.classList.remove('hidden');
542
+ uploadProgress.classList.add('hidden');
543
+ dataPreview.classList.remove('hidden');
544
+
545
+ // Calculate total pages
546
+ const totalRows = isFullDataLoaded ? fullCsvData.length : csvData.length;
547
+ totalPages = Math.ceil(totalRows / rowsPerPage);
548
+
549
+ // Update row count display
550
+ updateRowCountDisplay();
551
+
552
+ // Show/hide load full data button
553
+ if (isFullDataLoaded) {
554
+ loadFullDataBtn.classList.add('hidden');
555
+ } else {
556
+ loadFullDataBtn.classList.remove('hidden');
557
+ loadFullDataBtn.disabled = false;
558
+ loadFullDataBtn.innerHTML = '<i class="fas fa-database mr-2"></i> Load Full Dataset';
559
+ }
560
+
561
+ // Populate column list
562
+ columnList.innerHTML = '';
563
+ headers.forEach(header => {
564
+ const columnItem = document.createElement('div');
565
+ columnItem.className = 'column-selector p-2 rounded-md cursor-pointer flex items-center';
566
+ columnItem.innerHTML = `
567
+ <input type="checkbox" id="col-${header}" class="hidden column-checkbox">
568
+ <label for="col-${header}" class="flex items-center w-full cursor-pointer">
569
+ <span class="w-5 h-5 inline-block border border-gray-300 rounded mr-2 flex-shrink-0"></span>
570
+ <span class="truncate flex-grow">${header}</span>
571
+ <select class="column-type-selector text-xs border rounded px-1 py-0.5 ml-2" data-column="${header}">
572
+ <option value="string">Text</option>
573
+ <option value="number">Number</option>
574
+ <option value="date">Date</option>
575
+ <option value="boolean">Boolean</option>
576
+ </select>
577
+ </label>
578
+ `;
579
+
580
+ // Add click event to the checkbox label
581
+ const checkboxLabel = columnItem.querySelector('label');
582
+ checkboxLabel.addEventListener('click', (e) => {
583
+ e.stopPropagation(); // Prevent event bubbling
584
+ toggleColumnSelection(header, columnItem);
585
+ });
586
+
587
+ columnList.appendChild(columnItem);
588
+ });
589
+
590
+ // Populate table headers
591
+ tableHeader.innerHTML = '<th class="row-number">#</th>'; // Reset with row number column
592
+ headers.forEach(header => {
593
+ const th = document.createElement('th');
594
+ th.className = 'px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider';
595
+ th.textContent = header;
596
+ th.dataset.column = header;
597
+ tableHeader.appendChild(th);
598
+ });
599
+
600
+ // Set up pagination
601
+ updatePagination();
602
+
603
+ // Render the first page of data
604
+ renderTablePage();
605
+ }
606
+
607
+ function renderTablePage() {
608
+ // Clear existing table rows
609
+ tableBody.innerHTML = '';
610
+
611
+ // Get the data to display (either full data or preview)
612
+ const dataToShow = isFullDataLoaded ? fullCsvData : csvData;
613
+ const totalRows = dataToShow.length;
614
+
615
+ // Calculate start and end index for current page
616
+ const startIndex = (currentPage - 1) * rowsPerPage;
617
+ const endIndex = Math.min(startIndex + rowsPerPage, totalRows);
618
+
619
+ // Create rows for the current page
620
+ for (let i = startIndex; i < endIndex; i++) {
621
+ const row = dataToShow[i];
622
+ const tr = document.createElement('tr');
623
+ tr.className = i % 2 === 0 ? 'bg-white' : 'bg-gray-50';
624
+
625
+ // Add row number cell
626
+ const rowNumberCell = document.createElement('td');
627
+ rowNumberCell.className = 'row-number px-4 py-2 whitespace-nowrap text-sm';
628
+ rowNumberCell.textContent = i + 1;
629
+ tr.appendChild(rowNumberCell);
630
+
631
+ // Add data cells
632
+ headers.forEach(header => {
633
+ const td = document.createElement('td');
634
+ td.className = 'px-4 py-2 whitespace-nowrap text-sm text-gray-500';
635
+ const value = row[header];
636
+ const type = columnTypes[header];
637
+ const convertedValue = convertValue(value, type);
638
+
639
+ if (type === 'date' && convertedValue instanceof Date) {
640
+ td.textContent = convertedValue.toLocaleDateString();
641
+ } else if (type === 'boolean') {
642
+ td.textContent = convertedValue ? '✓' : '✗';
643
+ td.style.textAlign = 'center';
644
+ } else {
645
+ td.textContent = convertedValue || '';
646
+ }
647
+ tr.appendChild(td);
648
+ });
649
+
650
+ tableBody.appendChild(tr);
651
+ }
652
+
653
+ // Update row count display
654
+ updateRowCountDisplay();
655
+ }
656
+
657
+ function updateRowCountDisplay() {
658
+ const totalRows = isFullDataLoaded ? fullCsvData.length : csvData.length;
659
+ const startRow = (currentPage - 1) * rowsPerPage + 1;
660
+ const endRow = Math.min(currentPage * rowsPerPage, totalRows);
661
+
662
+ rowCountDisplay.textContent = isFullDataLoaded ?
663
+ `Showing rows ${startRow}-${endRow} of ${totalRows}` :
664
+ `Showing first ${csvData.length} rows of data (${startRow}-${endRow}). Click "Load Full Dataset" to analyze all data.`;
665
+ }
666
+
667
+ function updatePagination() {
668
+ const totalRows = isFullDataLoaded ? fullCsvData.length : csvData.length;
669
+ totalPages = Math.ceil(totalRows / rowsPerPage);
670
+
671
+ // Disable/enable pagination buttons
672
+ firstPageBtn.disabled = currentPage === 1;
673
+ prevPageBtn.disabled = currentPage === 1;
674
+ nextPageBtn.disabled = currentPage === totalPages;
675
+ lastPageBtn.disabled = currentPage === totalPages;
676
+
677
+ // Generate page numbers
678
+ pageNumbersContainer.innerHTML = '';
679
+
680
+ // Calculate range of pages to show
681
+ let startPage, endPage;
682
+ if (totalPages <= visiblePages) {
683
+ // Show all pages
684
+ startPage = 1;
685
+ endPage = totalPages;
686
+ } else {
687
+ // Calculate start and end pages
688
+ const maxPagesBeforeCurrent = Math.floor(visiblePages / 2);
689
+ const maxPagesAfterCurrent = Math.ceil(visiblePages / 2) - 1;
690
+
691
+ if (currentPage <= maxPagesBeforeCurrent) {
692
+ // Current page near the start
693
+ startPage = 1;
694
+ endPage = visiblePages;
695
+ } else if (currentPage + maxPagesAfterCurrent >= totalPages) {
696
+ // Current page near the end
697
+ startPage = totalPages - visiblePages + 1;
698
+ endPage = totalPages;
699
+ } else {
700
+ // Current page somewhere in the middle
701
+ startPage = currentPage - maxPagesBeforeCurrent;
702
+ endPage = currentPage + maxPagesAfterCurrent;
703
+ }
704
+ }
705
+
706
+ // Add page number buttons
707
+ for (let i = startPage; i <= endPage; i++) {
708
+ const pageBtn = document.createElement('button');
709
+ pageBtn.className = `pagination-btn bg-white border border-gray-300 rounded-md px-3 py-1 text-sm font-medium ${i === currentPage ? 'active bg-indigo-600 text-white' : 'text-gray-700 hover:bg-gray-50'}`;
710
+ pageBtn.textContent = i;
711
+ pageBtn.addEventListener('click', () => goToPage(i));
712
+ pageNumbersContainer.appendChild(pageBtn);
713
+ }
714
+ }
715
+
716
+ function goToPage(page) {
717
+ if (page < 1 || page > totalPages) return;
718
+
719
+ currentPage = page;
720
+ renderTablePage();
721
+ updatePagination();
722
+
723
+ // Scroll to top of table
724
+ document.querySelector('.data-table-container').scrollTop = 0;
725
+ }
726
+
727
+ function toggleColumnSelection(columnName, columnElement) {
728
+ const index = selectedColumns.indexOf(columnName);
729
+
730
+ if (index === -1) {
731
+ selectedColumns.push(columnName);
732
+ columnElement.classList.add('column-selected');
733
+ columnElement.querySelector('span:first-child').innerHTML = '<i class="fas fa-check text-indigo-600"></i>';
734
+ } else {
735
+ selectedColumns.splice(index, 1);
736
+ columnElement.classList.remove('column-selected');
737
+ columnElement.querySelector('span:first-child').innerHTML = '';
738
+ }
739
+
740
+ // Highlight selected columns in the table
741
+ document.querySelectorAll('th[data-column]').forEach(th => {
742
+ if (selectedColumns.includes(th.dataset.column)) {
743
+ th.classList.add('bg-indigo-50', 'text-indigo-700');
744
+ } else {
745
+ th.classList.remove('bg-indigo-50', 'text-indigo-700');
746
+ }
747
+ });
748
+
749
+ // Enable/disable analyze button
750
+ analyzeBtn.disabled = selectedColumns.length === 0;
751
+ }
752
+
753
+ function analyzeSelectedColumns() {
754
+ if (selectedColumns.length === 0) return;
755
+
756
+ // Show analysis results section
757
+ analysisResults.classList.remove('hidden');
758
+
759
+ // Generate statistics
760
+ generateStatistics();
761
+
762
+ // Create initial chart
763
+ createChart();
764
+ }
765
+
766
+ function generateStatistics() {
767
+ statsSummary.innerHTML = '';
768
+
769
+ // Use full dataset if loaded, otherwise use preview data
770
+ const dataToAnalyze = isFullDataLoaded ? fullCsvData : csvData;
771
+
772
+ selectedColumns.forEach(column => {
773
+ const values = dataToAnalyze.map(row => parseFloat(row[column])).filter(val => !isNaN(val));
774
+
775
+ if (values.length === 0) {
776
+ // Non-numeric column
777
+ const uniqueCount = new Set(dataToAnalyze.map(row => row[column])).size;
778
+
779
+ const card = document.createElement('div');
780
+ card.className = 'bg-gray-50 rounded-lg p-4 border border-gray-200';
781
+ card.innerHTML = `
782
+ <h4 class="font-medium text-gray-700 truncate">${column}</h4>
783
+ <div class="mt-2">
784
+ <div class="flex justify-between text-sm text-gray-600">
785
+ <span>Type:</span>
786
+ <span>Categorical</span>
787
+ </div>
788
+ <div class="flex justify-between text-sm text-gray-600 mt-1">
789
+ <span>Unique Values:</span>
790
+ <span>${uniqueCount}</span>
791
+ </div>
792
+ <div class="flex justify-between text-sm text-gray-600 mt-1">
793
+ <span>Missing Values:</span>
794
+ <span>${dataToAnalyze.filter(row => !row[column]).length}</span>
795
+ </div>
796
+ <div class="flex justify-between text-sm text-gray-600 mt-1">
797
+ <span>Total Rows:</span>
798
+ <span>${dataToAnalyze.length}</span>
799
+ </div>
800
+ </div>
801
+ `;
802
+ statsSummary.appendChild(card);
803
+ } else {
804
+ // Numeric column
805
+ const sum = values.reduce((a, b) => a + b, 0);
806
+ const mean = sum / values.length;
807
+ const sorted = [...values].sort((a, b) => a - b);
808
+ const median = sorted[Math.floor(sorted.length / 2)];
809
+ const min = sorted[0];
810
+ const max = sorted[sorted.length - 1];
811
+
812
+ // Calculate standard deviation
813
+ const squaredDiffs = values.map(val => Math.pow(val - mean, 2));
814
+ const variance = squaredDiffs.reduce((a, b) => a + b, 0) / values.length;
815
+ const stdDev = Math.sqrt(variance);
816
+
817
+ const card = document.createElement('div');
818
+ card.className = 'bg-gray-50 rounded-lg p-4 border border-gray-200';
819
+ card.innerHTML = `
820
+ <h4 class="font-medium text-gray-700 truncate">${column}</h4>
821
+ <div class="mt-2">
822
+ <div class="flex justify-between text-sm text-gray-600">
823
+ <span>Mean:</span>
824
+ <span>${mean.toFixed(2)}</span>
825
+ </div>
826
+ <div class="flex justify-between text-sm text-gray-600 mt-1">
827
+ <span>Median:</span>
828
+ <span>${median.toFixed(2)}</span>
829
+ </div>
830
+ <div class="flex justify-between text-sm text-gray-600 mt-1">
831
+ <span>Min/Max:</span>
832
+ <span>${min.toFixed(2)} / ${max.toFixed(2)}</span>
833
+ </div>
834
+ <div class="flex justify-between text-sm text-gray-600 mt-1">
835
+ <span>Std Dev:</span>
836
+ <span>${stdDev.toFixed(2)}</span>
837
+ </div>
838
+ <div class="flex justify-between text-sm text-gray-600 mt-1">
839
+ <span>Missing Values:</span>
840
+ <span>${dataToAnalyze.filter(row => !row[column]).length}</span>
841
+ </div>
842
+ <div class="flex justify-between text-sm text-gray-600 mt-1">
843
+ <span>Total Rows:</span>
844
+ <span>${dataToAnalyze.length}</span>
845
+ </div>
846
+ </div>
847
+ `;
848
+ statsSummary.appendChild(card);
849
+ }
850
+ });
851
+ }
852
+
853
+ function createChart() {
854
+ const ctx = chartCanvas.getContext('2d');
855
+ const chartType = chartTypeSelect.value;
856
+
857
+ if (chart) {
858
+ chart.destroy();
859
+ }
860
+
861
+ // Use full dataset if loaded, otherwise use preview data
862
+ const dataToAnalyze = isFullDataLoaded ? fullCsvData : csvData;
863
+
864
+ // Prepare data based on selected columns
865
+ const datasets = [];
866
+ const labels = Array.from({ length: dataToAnalyze.length }, (_, i) => `Row ${i + 1}`);
867
+
868
+ selectedColumns.forEach((column, i) => {
869
+ const values = dataToAnalyze.map(row => parseFloat(row[column]));
870
+ const isNumeric = values.every(v => !isNaN(v));
871
+
872
+ if (isNumeric) {
873
+ datasets.push({
874
+ label: column,
875
+ data: values,
876
+ backgroundColor: getColor(i, 0.7),
877
+ borderColor: getColor(i, 1),
878
+ borderWidth: 1
879
+ });
880
+ } else {
881
+ // For categorical data, count occurrences
882
+ const valueCounts = {};
883
+ dataToAnalyze.forEach(row => {
884
+ const val = row[column] || 'Missing';
885
+ valueCounts[val] = (valueCounts[val] || 0) + 1;
886
+ });
887
+
888
+ // For pie charts, use the categorical data directly
889
+ if (chartType === 'pie' || chartType === 'doughnut') {
890
+ datasets.push({
891
+ label: column,
892
+ data: Object.values(valueCounts),
893
+ backgroundColor: Object.keys(valueCounts).map((_, i) => getColor(i, 0.7)),
894
+ borderColor: Object.keys(valueCounts).map((_, i) => getColor(i, 1)),
895
+ borderWidth: 1
896
+ });
897
+ } else {
898
+ // For other charts, show counts by category
899
+ datasets.push({
900
+ label: column,
901
+ data: Object.values(valueCounts),
902
+ backgroundColor: getColor(i, 0.7),
903
+ borderColor: getColor(i, 1),
904
+ borderWidth: 1
905
+ });
906
+ }
907
+ }
908
+ });
909
+
910
+ // Create chart based on type
911
+ if (chartType === 'pie' || chartType === 'doughnut') {
912
+ // For pie/doughnut, only show first selected column
913
+ const firstColumn = selectedColumns[0];
914
+ const valueCounts = {};
915
+ dataToAnalyze.forEach(row => {
916
+ const val = row[firstColumn] || 'Missing';
917
+ valueCounts[val] = (valueCounts[val] || 0) + 1;
918
+ });
919
+
920
+ chart = new Chart(ctx, {
921
+ type: chartType,
922
+ data: {
923
+ labels: Object.keys(valueCounts),
924
+ datasets: [{
925
+ data: Object.values(valueCounts),
926
+ backgroundColor: Object.keys(valueCounts).map((_, i) => getColor(i, 0.7)),
927
+ borderColor: '#fff',
928
+ borderWidth: 1
929
+ }]
930
+ },
931
+ options: {
932
+ responsive: true,
933
+ maintainAspectRatio: false,
934
+ plugins: {
935
+ title: {
936
+ display: true,
937
+ text: `Distribution of ${firstColumn}`
938
+ }
939
+ }
940
+ }
941
+ });
942
+ } else if (chartType === 'scatter') {
943
+ // For scatter plot, need exactly 2 numeric columns
944
+ const numericColumns = selectedColumns.filter(col => {
945
+ const values = dataToAnalyze.map(row => parseFloat(row[col]));
946
+ return values.every(v => !isNaN(v));
947
+ });
948
+
949
+ if (numericColumns.length >= 2) {
950
+ const xValues = dataToAnalyze.map(row => parseFloat(row[numericColumns[0]]));
951
+ const yValues = dataToAnalyze.map(row => parseFloat(row[numericColumns[1]]));
952
+
953
+ chart = new Chart(ctx, {
954
+ type: 'scatter',
955
+ data: {
956
+ datasets: [{
957
+ label: `${numericColumns[0]} vs ${numericColumns[1]}`,
958
+ data: xValues.map((x, i) => ({x, y: yValues[i]})),
959
+ backgroundColor: getColor(0, 0.7),
960
+ borderColor: getColor(0, 1),
961
+ borderWidth: 1,
962
+ pointRadius: 5
963
+ }]
964
+ },
965
+ options: {
966
+ responsive: true,
967
+ maintainAspectRatio: false,
968
+ scales: {
969
+ x: {
970
+ title: {
971
+ display: true,
972
+ text: numericColumns[0]
973
+ }
974
+ },
975
+ y: {
976
+ title: {
977
+ display: true,
978
+ text: numericColumns[1]
979
+ }
980
+ }
981
+ },
982
+ plugins: {
983
+ title: {
984
+ display: true,
985
+ text: `Scatter Plot: ${numericColumns[0]} vs ${numericColumns[1]}`
986
+ }
987
+ }
988
+ }
989
+ });
990
+ } else {
991
+ showError('Scatter plot requires at least 2 numeric columns');
992
+ }
993
+ } else if (chartType === 'histogram') {
994
+ // For histogram, need at least 1 numeric column
995
+ const numericColumns = selectedColumns.filter(col => {
996
+ const values = dataToAnalyze.map(row => parseFloat(row[col]));
997
+ return values.every(v => !isNaN(v));
998
+ });
999
+
1000
+ if (numericColumns.length > 0) {
1001
+ const column = numericColumns[0];
1002
+ const values = dataToAnalyze.map(row => parseFloat(row[column]));
1003
+
1004
+ // Calculate optimal bin count using Sturges' formula
1005
+ const binCount = Math.ceil(Math.log2(values.length) + 1);
1006
+
1007
+ // Find min and max values
1008
+ const min = Math.min(...values);
1009
+ const max = Math.max(...values);
1010
+ const range = max - min;
1011
+ const binSize = range / binCount;
1012
+
1013
+ // Create bins
1014
+ const bins = Array(binCount).fill(0);
1015
+ values.forEach(val => {
1016
+ const binIndex = Math.min(Math.floor((val - min) / binSize), binCount - 1);
1017
+ bins[binIndex]++;
1018
+ });
1019
+
1020
+ // Create labels for bins
1021
+ const binLabels = Array(binCount).fill().map((_, i) => {
1022
+ const start = min + i * binSize;
1023
+ const end = min + (i + 1) * binSize;
1024
+ return `${start.toFixed(2)} - ${end.toFixed(2)}`;
1025
+ });
1026
+
1027
+ chart = new Chart(ctx, {
1028
+ type: 'bar',
1029
+ data: {
1030
+ labels: binLabels,
1031
+ datasets: [{
1032
+ label: `Frequency of ${column}`,
1033
+ data: bins,
1034
+ backgroundColor: getColor(0, 0.7),
1035
+ borderColor: getColor(0, 1),
1036
+ borderWidth: 1
1037
+ }]
1038
+ },
1039
+ options: {
1040
+ responsive: true,
1041
+ maintainAspectRatio: false,
1042
+ scales: {
1043
+ y: {
1044
+ beginAtZero: true,
1045
+ title: {
1046
+ display: true,
1047
+ text: 'Frequency'
1048
+ }
1049
+ },
1050
+ x: {
1051
+ title: {
1052
+ display: true,
1053
+ text: column
1054
+ }
1055
+ }
1056
+ },
1057
+ plugins: {
1058
+ title: {
1059
+ display: true,
1060
+ text: `Histogram of ${column}`
1061
+ }
1062
+ }
1063
+ }
1064
+ });
1065
+ } else {
1066
+ showError('Histogram requires at least 1 numeric column');
1067
+ }
1068
+ } else {
1069
+ // For bar/line charts
1070
+ chart = new Chart(ctx, {
1071
+ type: chartType,
1072
+ data: {
1073
+ labels: labels.slice(0, 100), // Limit to 100 labels for performance
1074
+ datasets: datasets.map((dataset, i) => ({
1075
+ ...dataset,
1076
+ data: dataset.data.slice(0, 100) // Limit to 100 data points for performance
1077
+ }))
1078
+ },
1079
+ options: {
1080
+ responsive: true,
1081
+ maintainAspectRatio: false,
1082
+ scales: {
1083
+ y: {
1084
+ beginAtZero: true
1085
+ }
1086
+ },
1087
+ plugins: {
1088
+ title: {
1089
+ display: true,
1090
+ text: `Analysis of ${selectedColumns.join(', ')}`
1091
+ },
1092
+ tooltip: {
1093
+ mode: 'index',
1094
+ intersect: false
1095
+ }
1096
+ }
1097
+ }
1098
+ });
1099
+ }
1100
+ }
1101
+
1102
+ function updateChart() {
1103
+ createChart();
1104
+ }
1105
+
1106
+ function exportChart() {
1107
+ if (!chart) return;
1108
+
1109
+ const link = document.createElement('a');
1110
+ link.download = 'chart.png';
1111
+ link.href = chartCanvas.toDataURL('image/png');
1112
+ link.click();
1113
+ }
1114
+
1115
+ function resetAnalysis() {
1116
+ selectedColumns = [];
1117
+ analysisResults.classList.add('hidden');
1118
+
1119
+ // Reset column selections
1120
+ document.querySelectorAll('.column-selector').forEach(el => {
1121
+ el.classList.remove('column-selected');
1122
+ el.querySelector('span:first-child').innerHTML = '';
1123
+ });
1124
+
1125
+ // Reset table header highlights
1126
+ document.querySelectorAll('th[data-column]').forEach(th => {
1127
+ th.classList.remove('bg-indigo-50', 'text-indigo-700');
1128
+ });
1129
+
1130
+ // Disable analyze button
1131
+ analyzeBtn.disabled = true;
1132
+ }
1133
+
1134
+ function resetUploadUI() {
1135
+ uploadContainer.classList.remove('hidden');
1136
+ uploadProgress.classList.add('hidden');
1137
+ progressBar.style.width = '0%';
1138
+ progressPercentage.textContent = '0%';
1139
+ uploadedSize.textContent = '0 MB';
1140
+ fileInput.value = '';
1141
+
1142
+ if (uploadAbortController) {
1143
+ uploadAbortController.abort();
1144
+ uploadAbortController = null;
1145
+ }
1146
+ }
1147
+
1148
+ function updateProgress(uploaded, total) {
1149
+ const percentage = Math.round((uploaded / total) * 100);
1150
+ progressBar.style.width = `${percentage}%`;
1151
+ progressPercentage.textContent = `${percentage}%`;
1152
+ uploadedSize.textContent = formatFileSize(uploaded);
1153
+ }
1154
+
1155
+ function formatFileSize(bytes) {
1156
+ if (bytes < 1024) return bytes + ' B';
1157
+ else if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
1158
+ else if (bytes < 1073741824) return (bytes / 1048576).toFixed(1) + ' MB';
1159
+ else return (bytes / 1073741824).toFixed(1) + ' GB';
1160
+ }
1161
+
1162
+ function getColor(index, opacity = 1) {
1163
+ const colors = [
1164
+ `rgba(79, 70, 229, ${opacity})`, // indigo
1165
+ `rgba(220, 38, 38, ${opacity})`, // red
1166
+ `rgba(5, 150, 105, ${opacity})`, // emerald
1167
+ `rgba(234, 88, 12, ${opacity})`, // orange
1168
+ `rgba(124, 58, 237, ${opacity})`, // purple
1169
+ `rgba(8, 145, 178, ${opacity})`, // cyan
1170
+ `rgba(202, 138, 4, ${opacity})`, // amber
1171
+ `rgba(22, 163, 74, ${opacity})`, // green
1172
+ `rgba(217, 70, 239, ${opacity})`, // pink
1173
+ `rgba(239, 68, 68, ${opacity})` // rose
1174
+ ];
1175
+ return colors[index % colors.length];
1176
+ }
1177
+
1178
+ function showError(message) {
1179
+ alert(message); // In a real app, you'd use a more elegant error display
1180
+ }
1181
+ </script>
1182
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=anzuo/deepsite" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
1183
+ </html>
prompts.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ 选择列时同时指定x,y轴