alterzick commited on
Commit
76c82b6
·
verified ·
1 Parent(s): 6df924e

Add 2 files

Browse files
Files changed (2) hide show
  1. index.html +654 -156
  2. prompts.txt +2 -1
index.html CHANGED
@@ -99,6 +99,98 @@
99
  z-index: 1000;
100
  white-space: nowrap;
101
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  </style>
103
  </head>
104
  <body class="bg-gradient-to-br from-blue-50 to-indigo-100 min-h-screen font-sans">
@@ -149,16 +241,37 @@
149
 
150
  <div>
151
  <label class="block text-sm font-medium text-gray-700 mb-2">Data Points</label>
152
- <div id="data-points-container" class="space-y-2 max-h-40 overflow-y-auto custom-scrollbar pr-2">
153
- <div class="data-point">
154
- <input type="text" class="x-value w-1/3" placeholder="X">
155
- <input type="number" class="y-value w-1/3" placeholder="Y">
156
- <i class="fas fa-trash remove-point"></i>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
  </div>
158
  </div>
159
- <button id="add-point" class="mt-2 flex items-center text-sm text-indigo-600 hover:text-indigo-800">
160
- <i class="fas fa-plus mr-1"></i> Add Data Point
161
- </button>
162
  </div>
163
  </div>
164
 
@@ -167,12 +280,26 @@
167
  <div>
168
  <label class="block text-sm font-medium text-gray-700 mb-1">Upload CSV File</label>
169
  <input type="file" id="csv-file" accept=".csv" class="w-full px-3 py-2 border border-gray-300 rounded-lg file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-medium file:bg-indigo-50 file:text-indigo-700 hover:file:bg-indigo-100">
170
- <p class="text-xs text-gray-500 mt-1">Upload a CSV file with two columns (X,Y)</p>
171
  </div>
172
 
173
  <div id="csv-preview" class="hidden">
174
- <h3 class="text-sm font-medium text-gray-700 mb-2">Preview</h3>
175
  <div id="csv-content" class="bg-gray-50 p-3 rounded-lg text-xs max-h-32 overflow-y-auto custom-scrollbar"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  </div>
177
  </div>
178
  </div>
@@ -189,44 +316,50 @@
189
  <select id="chart-type" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
190
  <option value="bar">Bar Chart</option>
191
  <option value="line">Line Chart</option>
192
- <option value="pie">Pie Chart</option>
193
- <option value="doughnut">Doughnut Chart</option>
194
  <option value="radar">Radar Chart</option>
195
  <option value="polarArea">Polar Area</option>
196
  <option value="bubble">Bubble Chart</option>
 
197
  </select>
198
  </div>
199
 
200
- <div id="line-options" class="space-y-3">
201
  <div class="flex items-center">
202
- <input type="checkbox" id="show-lines" class="mr-2 h-4 w-4 text-indigo-600 rounded focus:ring-indigo-500">
203
  <label for="show-lines" class="text-sm font-medium text-gray-700">Show Lines</label>
204
  </div>
205
  <div class="flex items-center">
206
- <input type="checkbox" id="show-points" class="mr-2 h-4 w-4 text-indigo-600 rounded focus:ring-indigo-500">
207
  <label for="show-points" class="text-sm font-medium text-gray-700">Show Points</label>
208
  </div>
209
  </div>
210
 
 
 
 
 
 
 
 
211
  <div id="color-scheme">
212
  <label class="block text-sm font-medium text-gray-700 mb-1">Color Scheme</label>
213
  <div class="grid grid-cols-3 gap-2">
214
- <button class="color-option p-2 rounded-lg flex items-center justify-center" data-colors='["#3B82F6", "#1D4ED8", "#1E40AF", "#1E3A8A", "#172554"]'>
215
- <div class="w-6 h-6 bg-blue-500 rounded-full"></div>
216
  </button>
217
- <button class="color-option p-2 rounded-lg flex items-center justify-center" data-colors='["#EC4899", "#DB2777", "#BE185D", "#9D174D", "#7E113B"]'>
218
  <div class="w-6 h-6 bg-pink-500 rounded-full"></div>
219
  </button>
220
- <button class="color-option p-2 rounded-lg flex items-center justify-center" data-colors='["#10B981", "#059669", "#047857", "#065F46", "#064E3B"]'>
221
  <div class="w-6 h-6 bg-green-500 rounded-full"></div>
222
  </button>
223
- <button class="color-option p-2 rounded-lg flex items-center justify-center" data-colors='["#F59E0B", "#D97706", "#B45309", "#92400E", "#78350F"]'>
224
  <div class="w-6 h-6 bg-amber-500 rounded-full"></div>
225
  </button>
226
- <button class="color-option p-2 rounded-lg flex items-center justify-center" data-colors='["#8B5CF6", "#7C3AED", "#6D28D9", "#5B21B6", "#4C1D95"]'>
227
  <div class="w-6 h-6 bg-purple-500 rounded-full"></div>
228
  </button>
229
- <button class="color-option p-2 rounded-lg flex items-center justify-center" data-colors='["#06B6D4", "#0891B2", "#0E7490", "#155E75", "#164E63"]'>
230
  <div class="w-6 h-6 bg-cyan-500 rounded-full"></div>
231
  </button>
232
  </div>
@@ -261,6 +394,9 @@
261
  <i class="fas fa-chart-bar mr-2 text-indigo-600"></i> <span id="chart-title-display">Your Chart</span>
262
  </h2>
263
  <div class="flex space-x-2">
 
 
 
264
  <button id="fullscreen-btn" class="p-2 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-lg transition-colors duration-200">
265
  <i class="fas fa-expand"></i>
266
  </button>
@@ -275,6 +411,13 @@
275
  <p class="text-sm">Configure your data and settings, then click "Generate Chart"</p>
276
  </div>
277
  </div>
 
 
 
 
 
 
 
278
  </div>
279
  </div>
280
  </div>
@@ -309,12 +452,16 @@
309
  const csvFile = document.getElementById('csv-file');
310
  const csvPreview = document.getElementById('csv-preview');
311
  const csvContent = document.getElementById('csv-content');
312
- const addPointBtn = document.getElementById('add-point');
313
- const dataPointsContainer = document.getElementById('data-points-container');
 
 
314
  const chartType = document.getElementById('chart-type');
315
  const lineOptions = document.getElementById('line-options');
 
316
  const showLines = document.getElementById('show-lines');
317
  const showPoints = document.getElementById('show-points');
 
318
  const colorScheme = document.getElementById('color-scheme');
319
  const colorOptions = document.querySelectorAll('.color-option');
320
  const animationDuration = document.getElementById('animation-duration');
@@ -322,8 +469,6 @@
322
  const generateBtn = document.getElementById('generate-btn');
323
  const downloadBtn = document.getElementById('download-btn');
324
  const chartTitle = document.getElementById('chart-title');
325
- const xLabel = document.getElementById('x-label');
326
- const yLabel = document.getElementById('y-label');
327
  const chartTitleDisplay = document.getElementById('chart-title-display');
328
  const emptyState = document.getElementById('empty-state');
329
  const toast = document.getElementById('toast');
@@ -334,11 +479,23 @@
334
  const fullscreenTitle = document.getElementById('fullscreen-title');
335
  const graph = document.getElementById('graph');
336
  const fullscreenChart = document.getElementById('fullscreen-chart');
 
 
 
 
 
 
 
 
 
337
 
338
  // Chart instance
339
  let chart = null;
340
  let fullscreenChartInstance = null;
341
- let currentColors = ['#3B82F6', '#1D4ED8', '#1E40AF', '#1E3A8A', '#172554'];
 
 
 
342
 
343
  // Initialize chart with empty data
344
  function initChart() {
@@ -346,20 +503,15 @@
346
  type: 'bar',
347
  data: {
348
  labels: [],
349
- datasets: [{
350
- label: 'Data',
351
- data: [],
352
- backgroundColor: currentColors[0],
353
- borderColor: currentColors[0],
354
- borderWidth: 1
355
- }]
356
  },
357
  options: {
358
  responsive: true,
359
  maintainAspectRatio: false,
360
  plugins: {
361
  legend: {
362
- display: false
 
363
  },
364
  tooltip: {
365
  backgroundColor: 'rgba(0, 0, 0, 0.8)',
@@ -368,7 +520,7 @@
368
  borderColor: '#333',
369
  borderWidth: 1,
370
  cornerRadius: 6,
371
- displayColors: false
372
  }
373
  },
374
  scales: {
@@ -416,29 +568,30 @@
416
  manualInput.classList.add('hidden');
417
  });
418
 
419
- // Add new data point
420
- addPointBtn.addEventListener('click', function() {
421
  const point = document.createElement('div');
422
- point.className = 'data-point';
423
  point.innerHTML = `
424
- <input type="text" class="x-value w-1/3" placeholder="X">
425
- <input type="number" class="y-value w-1/3" placeholder="Y">
426
- <i class="fas fa-trash remove-point"></i>
 
427
  `;
428
- dataPointsContainer.appendChild(point);
429
 
430
  // Add event listener to remove button
431
- point.querySelector('.remove-point').addEventListener('click', function() {
432
- dataPointsContainer.removeChild(point);
433
  });
434
  });
435
 
436
- // Remove data point
437
- dataPointsContainer.addEventListener('click', function(e) {
438
- if (e.target.classList.contains('remove-point')) {
439
- const point = e.target.closest('.data-point');
440
- if (dataPointsContainer.children.length > 1) {
441
- dataPointsContainer.removeChild(point);
442
  }
443
  }
444
  });
@@ -451,8 +604,79 @@
451
  const reader = new FileReader();
452
  reader.onload = function(e) {
453
  const content = e.target.result;
454
- csvContent.textContent = content.split('\n').slice(0, 10).join('\n') + (content.split('\n').length > 10 ? '\n...' : '');
455
- csvPreview.classList.remove('hidden');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
456
  };
457
  reader.readAsText(file);
458
  });
@@ -480,17 +704,19 @@
480
  chartType.addEventListener('change', function() {
481
  if (this.value === 'line') {
482
  lineOptions.classList.remove('hidden');
 
 
 
 
483
  } else {
484
  lineOptions.classList.add('hidden');
 
485
  }
486
  });
487
 
488
  // Generate chart
489
  generateBtn.addEventListener('click', function() {
490
  // Get data based on input method
491
- let labels = [];
492
- let data = [];
493
-
494
  if (manualInput.classList.contains('hidden')) {
495
  // File input method
496
  const file = csvFile.files[0];
@@ -499,78 +725,189 @@
499
  return;
500
  }
501
 
502
- const reader = new FileReader();
503
- reader.onload = function(e) {
504
- const content = e.target.result;
505
- const rows = content.split('\n');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
506
 
507
- for (let i = 0; i < rows.length; i++) {
508
- if (rows[i].trim() === '') continue;
509
-
510
- const cols = rows[i].split(',');
511
- if (cols.length >= 2) {
512
- labels.push(cols[0].trim());
513
- data.push(parseFloat(cols[1].trim()));
 
514
  }
515
  }
516
 
517
- createChart(labels, data);
518
- };
519
- reader.readAsText(file);
 
520
  } else {
521
  // Manual input method
522
- const points = dataPointsContainer.querySelectorAll('.data-point');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
523
  let isValid = true;
 
524
 
525
  points.forEach(point => {
526
- const xValue = point.querySelector('.x-value').value;
527
- const yValue = point.querySelector('.y-value').value;
 
 
 
 
 
 
528
 
529
- if (xValue && yValue !== '') {
530
- labels.push(xValue);
531
- data.push(parseFloat(yValue));
532
- } else if (xValue || yValue !== '') {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
533
  isValid = false;
534
  }
535
  });
536
 
 
 
 
537
  if (!isValid) {
538
- showToast('Please fill in all data points');
539
  return;
540
  }
541
 
542
- if (labels.length === 0) {
543
- showToast('Please add at least one data point');
544
  return;
545
  }
546
 
547
- createChart(labels, data);
 
 
 
 
 
548
  }
549
  });
550
 
551
  // Create chart with data
552
- function createChart(labels, data) {
553
  // Update chart title
554
  const title = chartTitle.value || 'Your Chart';
555
  chartTitleDisplay.textContent = title;
556
 
 
 
 
 
 
 
 
 
557
  // Destroy existing chart
558
  if (chart) {
559
  chart.destroy();
560
  }
561
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
562
  // Chart configuration
563
  const chartConfig = {
564
  type: chartType.value,
565
  data: {
566
  labels: labels,
567
- datasets: [{
568
- label: 'Data',
569
- data: data,
570
- backgroundColor: currentColors[0],
571
- borderColor: currentColors[0],
572
- borderWidth: 1
573
- }]
574
  },
575
  options: {
576
  responsive: true,
@@ -587,7 +924,8 @@
587
  }
588
  },
589
  legend: {
590
- display: false
 
591
  },
592
  tooltip: {
593
  backgroundColor: 'rgba(0, 0, 0, 0.8)',
@@ -596,7 +934,7 @@
596
  borderColor: '#333',
597
  borderWidth: 1,
598
  cornerRadius: 6,
599
- displayColors: false
600
  }
601
  },
602
  scales: {
@@ -613,83 +951,191 @@
613
  text: yLabel.value || 'Value',
614
  color: '#6b7280'
615
  }
616
- },
617
- x: {
618
- grid: {
619
- color: 'rgba(0, 0, 0, 0.05)'
620
- },
621
- ticks: {
622
- color: '#6b7280'
623
- },
624
- title: {
625
- display: true,
626
- text: xLabel.value || 'Category',
627
- color: '#6b7280'
628
- }
629
  }
630
  }
631
  }
632
  };
633
 
634
- // Special configurations for different chart types
635
- if (chartType.value === 'bar') {
636
- chartConfig.data.datasets[0].backgroundColor = currentColors.map(color =>
637
- typeof color === 'string' ? color : color.backgroundColor
638
- ).slice(0, data.length);
639
- } else if (chartType.value === 'pie' || chartType.value === 'doughnut') {
640
- chartConfig.data.datasets[0].backgroundColor = currentColors.slice(0, data.length);
641
- chartConfig.data.datasets[0].borderColor = currentColors.slice(0, data.length).map(c => '#ffffff');
642
- chartConfig.options.plugins.legend = {
643
- display: true,
644
- position: 'bottom'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
645
  };
646
- chartConfig.options.scales = {};
647
- } else if (chartType.value === 'radar') {
648
- chartConfig.data.datasets[0].backgroundColor = 'rgba(59, 130, 246, 0.2)';
649
- chartConfig.data.datasets[0].borderColor = currentColors[0];
650
- } else if (chartType.value === 'polarArea') {
651
- chartConfig.data.datasets[0].backgroundColor = currentColors.slice(0, data.length);
652
- } else if (chartType.value === 'line') {
653
- chartConfig.data.datasets[0].backgroundColor = 'rgba(59, 130, 246, 0.1)';
654
- chartConfig.data.datasets[0].borderColor = currentColors[0];
655
- chartConfig.data.datasets[0].borderWidth = 3;
656
- chartConfig.data.datasets[0].pointBackgroundColor = currentColors[0];
657
- chartConfig.data.datasets[0].pointBorderColor = '#fff';
658
- chartConfig.data.datasets[0].pointBorderWidth = 2;
659
- chartConfig.data.datasets[0].pointRadius = showPoints.checked ? 4 : 0;
660
- chartConfig.options.scales.x.title.display = true;
661
-
662
- if (!showLines.checked) {
663
- chartConfig.data.datasets[0].borderWidth = 0;
664
- }
665
-
666
- if (!showPoints.checked) {
667
- chartConfig.data.datasets[0].pointRadius = 0;
668
- }
669
- } else if (chartType.value === 'bubble') {
670
- // Convert data to bubble format
671
- const bubbleData = [];
672
- for (let i = 0; i < data.length; i++) {
673
- bubbleData.push({
674
- x: i,
675
- y: data[i],
676
- r: Math.abs(data[i]) * 2 + 5
677
- });
678
- }
679
-
680
- chartConfig.data.datasets[0].data = bubbleData;
681
- chartConfig.data.datasets[0].backgroundColor = currentColors[0];
682
- chartConfig.data.labels = [];
683
- chartConfig.options.scales.x.min = -1;
684
- chartConfig.options.scales.x.max = data.length;
685
  }
686
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
687
  // Create new chart
688
  chart = new Chart(graph, chartConfig);
689
 
690
  // Hide empty state
691
  emptyState.classList.add('hidden');
692
 
 
 
 
 
 
 
693
  // Show success message
694
  showToast('Chart generated successfully!');
695
  }
@@ -713,6 +1159,58 @@
713
  showToast(`Chart downloaded as ${filename}`);
714
  });
715
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
716
  // Fullscreen functionality
717
  fullscreenBtn.addEventListener('click', function() {
718
  fullscreenModal.classList.remove('hidden');
@@ -760,8 +1258,8 @@
760
  }
761
 
762
  // Initialize with empty data point if none exists
763
- if (dataPointsContainer.children.length === 0) {
764
- addPointBtn.click();
765
  }
766
  });
767
  </script>
 
99
  z-index: 1000;
100
  white-space: nowrap;
101
  }
102
+
103
+ .dataset-selector {
104
+ margin-top: 8px;
105
+ padding: 8px;
106
+ border: 1px solid #e5e7eb;
107
+ border-radius: 6px;
108
+ background-color: #f9fafb;
109
+ }
110
+
111
+ .dataset-option {
112
+ display: flex;
113
+ align-items: center;
114
+ padding: 4px 8px;
115
+ margin: 4px 0;
116
+ cursor: pointer;
117
+ border-radius: 4px;
118
+ transition: background-color 0.2s;
119
+ }
120
+
121
+ .dataset-option:hover {
122
+ background-color: #f3f4f6;
123
+ }
124
+
125
+ .dataset-option.selected {
126
+ background-color: #e0f2fe;
127
+ border: 1px solid #0ea5e9;
128
+ }
129
+
130
+ .multi-input-container {
131
+ border: 1px solid #e5e7eb;
132
+ border-radius: 6px;
133
+ padding: 8px;
134
+ margin-top: 8px;
135
+ background-color: #f9fafb;
136
+ }
137
+
138
+ .multi-data-point {
139
+ display: flex;
140
+ margin-bottom: 6px;
141
+ align-items: center;
142
+ }
143
+
144
+ .multi-data-point input {
145
+ margin-right: 6px;
146
+ padding: 4px 6px;
147
+ border: 1px solid #d1d5db;
148
+ border-radius: 4px;
149
+ font-size: 14px;
150
+ }
151
+
152
+ .remove-multi-point {
153
+ color: #ef4444;
154
+ cursor: pointer;
155
+ margin-left: 6px;
156
+ font-size: 16px;
157
+ }
158
+
159
+ /* Custom select styling */
160
+ .select-wrapper {
161
+ position: relative;
162
+ }
163
+
164
+ .select-wrapper::after {
165
+ content: '▼';
166
+ font-size: 10px;
167
+ color: #6b7280;
168
+ position: absolute;
169
+ right: 10px;
170
+ top: 50%;
171
+ transform: translateY(-50%);
172
+ pointer-events: none;
173
+ }
174
+
175
+ .color-preview {
176
+ width: 16px;
177
+ height: 16px;
178
+ border-radius: 50%;
179
+ display: inline-block;
180
+ margin-right: 6px;
181
+ vertical-align: middle;
182
+ }
183
+
184
+ /* Animation for chart update */
185
+ @keyframes chartUpdate {
186
+ 0% { transform: scale(1); }
187
+ 50% { transform: scale(1.02); }
188
+ 100% { transform: scale(1); }
189
+ }
190
+
191
+ .chart-update {
192
+ animation: chartUpdate 0.3s ease-out;
193
+ }
194
  </style>
195
  </head>
196
  <body class="bg-gradient-to-br from-blue-50 to-indigo-100 min-h-screen font-sans">
 
241
 
242
  <div>
243
  <label class="block text-sm font-medium text-gray-700 mb-2">Data Points</label>
244
+ <div class="flex items-center mb-2">
245
+ <span class="text-xs text-gray-500 bg-gray-100 px-2 py-1 rounded">Multiple Columns Mode</span>
246
+ </div>
247
+ <div id="multi-data-container" class="multi-input-container">
248
+ <div id="multi-data-points-container" class="space-y-2 max-h-40 overflow-y-auto custom-scrollbar pr-2">
249
+ <div class="multi-data-point">
250
+ <input type="text" class="category-input w-1/4" placeholder="Category">
251
+ <input type="number" class="dataset1-input w-1/4" placeholder="Dataset 1">
252
+ <input type="number" class="dataset2-input w-1/4" placeholder="Dataset 2">
253
+ <i class="fas fa-trash remove-multi-point"></i>
254
+ </div>
255
+ </div>
256
+ <button id="add-multi-point" class="mt-2 flex items-center text-sm text-indigo-600 hover:text-indigo-800">
257
+ <i class="fas fa-plus mr-1"></i> Add Data Point
258
+ </button>
259
+ </div>
260
+ </div>
261
+
262
+ <!-- Dataset Configuration -->
263
+ <div id="dataset-config" class="mt-4">
264
+ <label class="block text-sm font-medium text-gray-700 mb-2">Dataset Labels</label>
265
+ <div id="dataset-labels-container" class="space-y-2">
266
+ <div class="flex">
267
+ <input type="text" id="dataset1-label" value="Dataset 1" class="w-3/4 px-3 py-1 border border-gray-300 rounded-l-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
268
+ <div class="w-1/4 bg-blue-100 text-blue-800 px-2 py-1 rounded-r-lg text-center">Color</div>
269
+ </div>
270
+ <div class="flex">
271
+ <input type="text" id="dataset2-label" value="Dataset 2" class="w-3/4 px-3 py-1 border border-gray-300 rounded-l-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
272
+ <div class="w-1/4 bg-green-100 text-green-800 px-2 py-1 rounded-r-lg text-center">Color</div>
273
  </div>
274
  </div>
 
 
 
275
  </div>
276
  </div>
277
 
 
280
  <div>
281
  <label class="block text-sm font-medium text-gray-700 mb-1">Upload CSV File</label>
282
  <input type="file" id="csv-file" accept=".csv" class="w-full px-3 py-2 border border-gray-300 rounded-lg file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-medium file:bg-indigo-50 file:text-indigo-700 hover:file:bg-indigo-100">
283
+ <p class="text-xs text-gray-500 mt-1">Upload a CSV file (first row should contain headers)</p>
284
  </div>
285
 
286
  <div id="csv-preview" class="hidden">
287
+ <h3 class="text-sm font-medium text-gray-700 mb-2">Data Preview</h3>
288
  <div id="csv-content" class="bg-gray-50 p-3 rounded-lg text-xs max-h-32 overflow-y-auto custom-scrollbar"></div>
289
+
290
+ <div class="mt-3">
291
+ <label class="block text-sm font-medium text-gray-700 mb-2">Select Data to Plot</label>
292
+ <div class="select-wrapper">
293
+ <select id="x-axis-select" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
294
+ <option value="">Select X-axis column</option>
295
+ </select>
296
+ </div>
297
+
298
+ <div class="mt-2 dataset-selector">
299
+ <h4 class="text-sm font-medium text-gray-700 mb-1">Select Y-axis columns:</h4>
300
+ <div id="y-axis-options" class="space-y-1 max-h-32 overflow-y-auto custom-scrollbar"></div>
301
+ </div>
302
+ </div>
303
  </div>
304
  </div>
305
  </div>
 
316
  <select id="chart-type" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
317
  <option value="bar">Bar Chart</option>
318
  <option value="line">Line Chart</option>
 
 
319
  <option value="radar">Radar Chart</option>
320
  <option value="polarArea">Polar Area</option>
321
  <option value="bubble">Bubble Chart</option>
322
+ <option value="scatter">Scatter Chart</option>
323
  </select>
324
  </div>
325
 
326
+ <div id="line-options" class="space-y-3 hidden">
327
  <div class="flex items-center">
328
+ <input type="checkbox" id="show-lines" class="mr-2 h-4 w-4 text-indigo-600 rounded focus:ring-indigo-500" checked>
329
  <label for="show-lines" class="text-sm font-medium text-gray-700">Show Lines</label>
330
  </div>
331
  <div class="flex items-center">
332
+ <input type="checkbox" id="show-points" class="mr-2 h-4 w-4 text-indigo-600 rounded focus:ring-indigo-500" checked>
333
  <label for="show-points" class="text-sm font-medium text-gray-700">Show Points</label>
334
  </div>
335
  </div>
336
 
337
+ <div id="scatter-options" class="space-y-3 hidden">
338
+ <div class="flex items-center">
339
+ <input type="checkbox" id="show-trendline" class="mr-2 h-4 w-4 text-indigo-600 rounded focus:ring-indigo-500">
340
+ <label for="show-trendline" class="text-sm font-medium text-gray-700">Show Trendline</label>
341
+ </div>
342
+ </div>
343
+
344
  <div id="color-scheme">
345
  <label class="block text-sm font-medium text-gray-700 mb-1">Color Scheme</label>
346
  <div class="grid grid-cols-3 gap-2">
347
+ <button class="color-option p-2 rounded-lg flex items-center justify-center" data-colors='["#3B82F6", "#10B981", "#F59E0B", "#EF4444", "#8B5CF6", "#06B6D4", "#EC4899", "#F97316"]'>
348
+ <div class="w-6 h-6 bg-gradient-to-br from-blue-500 to-cyan-500 rounded-full"></div>
349
  </button>
350
+ <button class="color-option p-2 rounded-lg flex items-center justify-center" data-colors='["#EC4899", "#DB2777", "#BE185D", "#9D174D", "#7E113B", "#C2410C", "#9A3412", "#7C2D12"]'>
351
  <div class="w-6 h-6 bg-pink-500 rounded-full"></div>
352
  </button>
353
+ <button class="color-option p-2 rounded-lg flex items-center justify-center" data-colors='["#10B981", "#059669", "#047857", "#065F46", "#064E3B", "#16A34A", "#22C55E", "#4ADE80"]'>
354
  <div class="w-6 h-6 bg-green-500 rounded-full"></div>
355
  </button>
356
+ <button class="color-option p-2 rounded-lg flex items-center justify-center" data-colors='["#F59E0B", "#D97706", "#B45309", "#92400E", "#78350F", "#F97316", "#EA580C", "#C2410C"]'>
357
  <div class="w-6 h-6 bg-amber-500 rounded-full"></div>
358
  </button>
359
+ <button class="color-option p-2 rounded-lg flex items-center justify-center" data-colors='["#8B5CF6", "#7C3AED", "#6D28D9", "#5B21B6", "#4C1D95", "#A855F7", "#C026D3", "#BE185D"]'>
360
  <div class="w-6 h-6 bg-purple-500 rounded-full"></div>
361
  </button>
362
+ <button class="color-option p-2 rounded-lg flex items-center justify-center" data-colors='["#06B6D4", "#0891B2", "#0E7490", "#155E75", "#164E63", "#38BDF8", "#0EA5E9", "#0284C7"]'>
363
  <div class="w-6 h-6 bg-cyan-500 rounded-full"></div>
364
  </button>
365
  </div>
 
394
  <i class="fas fa-chart-bar mr-2 text-indigo-600"></i> <span id="chart-title-display">Your Chart</span>
395
  </h2>
396
  <div class="flex space-x-2">
397
+ <button id="export-data-btn" class="p-2 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-lg transition-colors duration-200" title="Export Data">
398
+ <i class="fas fa-file-export"></i>
399
+ </button>
400
  <button id="fullscreen-btn" class="p-2 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-lg transition-colors duration-200">
401
  <i class="fas fa-expand"></i>
402
  </button>
 
411
  <p class="text-sm">Configure your data and settings, then click "Generate Chart"</p>
412
  </div>
413
  </div>
414
+
415
+ <div id="chart-info" class="mt-4 text-sm text-gray-500 hidden">
416
+ <div class="flex justify-between">
417
+ <span>Chart Statistics:</span>
418
+ <span id="data-count">0 points</span>
419
+ </div>
420
+ </div>
421
  </div>
422
  </div>
423
  </div>
 
452
  const csvFile = document.getElementById('csv-file');
453
  const csvPreview = document.getElementById('csv-preview');
454
  const csvContent = document.getElementById('csv-content');
455
+ const addMultiPointBtn = document.getElementById('add-multi-point');
456
+ const multiDataPointsContainer = document.getElementById('multi-data-points-container');
457
+ const xLabel = document.getElementById('x-label');
458
+ const yLabel = document.getElementById('y-label');
459
  const chartType = document.getElementById('chart-type');
460
  const lineOptions = document.getElementById('line-options');
461
+ const scatterOptions = document.getElementById('scatter-options');
462
  const showLines = document.getElementById('show-lines');
463
  const showPoints = document.getElementById('show-points');
464
+ const showTrendline = document.getElementById('show-trendline');
465
  const colorScheme = document.getElementById('color-scheme');
466
  const colorOptions = document.querySelectorAll('.color-option');
467
  const animationDuration = document.getElementById('animation-duration');
 
469
  const generateBtn = document.getElementById('generate-btn');
470
  const downloadBtn = document.getElementById('download-btn');
471
  const chartTitle = document.getElementById('chart-title');
 
 
472
  const chartTitleDisplay = document.getElementById('chart-title-display');
473
  const emptyState = document.getElementById('empty-state');
474
  const toast = document.getElementById('toast');
 
479
  const fullscreenTitle = document.getElementById('fullscreen-title');
480
  const graph = document.getElementById('graph');
481
  const fullscreenChart = document.getElementById('fullscreen-chart');
482
+ const xAxisSelect = document.getElementById('x-axis-select');
483
+ const yAxisOptions = document.getElementById('y-axis-options');
484
+ const chartInfo = document.getElementById('chart-info');
485
+ const dataCount = document.getElementById('data-count');
486
+ const exportDataBtn = document.getElementById('export-data-btn');
487
+
488
+ // Dataset elements
489
+ const dataset1Label = document.getElementById('dataset1-label');
490
+ const dataset2Label = document.getElementById('dataset2-label');
491
 
492
  // Chart instance
493
  let chart = null;
494
  let fullscreenChartInstance = null;
495
+ let currentColors = ['#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', '#06B6D4', '#EC4899', '#F97316'];
496
+ let csvHeaders = [];
497
+ let csvData = [];
498
+ let selectedYColumns = [];
499
 
500
  // Initialize chart with empty data
501
  function initChart() {
 
503
  type: 'bar',
504
  data: {
505
  labels: [],
506
+ datasets: []
 
 
 
 
 
 
507
  },
508
  options: {
509
  responsive: true,
510
  maintainAspectRatio: false,
511
  plugins: {
512
  legend: {
513
+ display: true,
514
+ position: 'bottom'
515
  },
516
  tooltip: {
517
  backgroundColor: 'rgba(0, 0, 0, 0.8)',
 
520
  borderColor: '#333',
521
  borderWidth: 1,
522
  cornerRadius: 6,
523
+ displayColors: true
524
  }
525
  },
526
  scales: {
 
568
  manualInput.classList.add('hidden');
569
  });
570
 
571
+ // Add new multi data point
572
+ addMultiPointBtn.addEventListener('click', function() {
573
  const point = document.createElement('div');
574
+ point.className = 'multi-data-point';
575
  point.innerHTML = `
576
+ <input type="text" class="category-input w-1/4" placeholder="Category">
577
+ <input type="number" class="dataset1-input w-1/4" placeholder="Dataset 1">
578
+ <input type="number" class="dataset2-input w-1/4" placeholder="Dataset 2">
579
+ <i class="fas fa-trash remove-multi-point"></i>
580
  `;
581
+ multiDataPointsContainer.appendChild(point);
582
 
583
  // Add event listener to remove button
584
+ point.querySelector('.remove-multi-point').addEventListener('click', function() {
585
+ multiDataPointsContainer.removeChild(point);
586
  });
587
  });
588
 
589
+ // Remove multi data point
590
+ multiDataPointsContainer.addEventListener('click', function(e) {
591
+ if (e.target.classList.contains('remove-multi-point')) {
592
+ const point = e.target.closest('.multi-data-point');
593
+ if (multiDataPointsContainer.children.length > 1) {
594
+ multiDataPointsContainer.removeChild(point);
595
  }
596
  }
597
  });
 
604
  const reader = new FileReader();
605
  reader.onload = function(e) {
606
  const content = e.target.result;
607
+ const lines = content.split('\n');
608
+
609
+ // Parse CSV data
610
+ csvData = [];
611
+ for (let i = 0; i < lines.length; i++) {
612
+ if (lines[i].trim() === '') continue;
613
+
614
+ // Use a more robust CSV parsing method
615
+ const rowData = lines[i].split(',').map(cell => cell.trim().replace(/^"(.*)"$/, '$1'));
616
+ csvData.push(rowData);
617
+ }
618
+
619
+ // Extract headers from first row
620
+ if (csvData.length > 0) {
621
+ csvHeaders = csvData[0];
622
+
623
+ // Display preview
624
+ let previewHTML = '<table class="w-full text-xs">';
625
+ // Show headers
626
+ previewHTML += '<tr class="bg-gray-100">';
627
+ csvHeaders.forEach(header => {
628
+ previewHTML += `<th class="px-2 py-1 text-left border-b">${header}</th>`;
629
+ });
630
+ previewHTML += '</tr>';
631
+
632
+ // Show data rows (max 5)
633
+ for (let i = 1; i < Math.min(csvData.length, 6); i++) {
634
+ previewHTML += '<tr>';
635
+ csvData[i].forEach(cell => {
636
+ previewHTML += `<td class="px-2 py-1 border-b">${cell}</td>`;
637
+ });
638
+ previewHTML += '</tr>';
639
+ }
640
+
641
+ if (csvData.length > 6) {
642
+ previewHTML += '<tr><td colspan="' + csvHeaders.length + '" class="px-2 py-1 text-center text-gray-500">... and ' + (csvData.length - 6) + ' more rows</td></tr>';
643
+ }
644
+
645
+ previewHTML += '</table>';
646
+ csvContent.innerHTML = previewHTML;
647
+ csvPreview.classList.remove('hidden');
648
+
649
+ // Populate X-axis select
650
+ xAxisSelect.innerHTML = '<option value="">Select X-axis column</option>';
651
+ csvHeaders.forEach((header, index) => {
652
+ const option = document.createElement('option');
653
+ option.value = index;
654
+ option.textContent = header;
655
+ xAxisSelect.appendChild(option);
656
+ });
657
+
658
+ // Populate Y-axis options
659
+ yAxisOptions.innerHTML = '';
660
+ csvHeaders.forEach((header, index) => {
661
+ const optionDiv = document.createElement('div');
662
+ optionDiv.className = 'flex items-center dataset-option';
663
+ optionDiv.dataset.column = index;
664
+ optionDiv.innerHTML = `
665
+ <input type="checkbox" id="y-col-${index}" class="mr-2 h-4 w-4 text-indigo-600 rounded focus:ring-indigo-500">
666
+ <label for="y-col-${index}" class="text-sm font-medium text-gray-700">${header}</label>
667
+ `;
668
+ optionDiv.querySelector('input').addEventListener('change', function() {
669
+ if (this.checked) {
670
+ if (!selectedYColumns.includes(parseInt(index))) {
671
+ selectedYColumns.push(parseInt(index));
672
+ }
673
+ } else {
674
+ selectedYColumns = selectedYColumns.filter(col => col !== parseInt(index));
675
+ }
676
+ });
677
+ yAxisOptions.appendChild(optionDiv);
678
+ });
679
+ }
680
  };
681
  reader.readAsText(file);
682
  });
 
704
  chartType.addEventListener('change', function() {
705
  if (this.value === 'line') {
706
  lineOptions.classList.remove('hidden');
707
+ scatterOptions.classList.add('hidden');
708
+ } else if (this.value === 'scatter') {
709
+ lineOptions.classList.add('hidden');
710
+ scatterOptions.classList.remove('hidden');
711
  } else {
712
  lineOptions.classList.add('hidden');
713
+ scatterOptions.classList.add('hidden');
714
  }
715
  });
716
 
717
  // Generate chart
718
  generateBtn.addEventListener('click', function() {
719
  // Get data based on input method
 
 
 
720
  if (manualInput.classList.contains('hidden')) {
721
  // File input method
722
  const file = csvFile.files[0];
 
725
  return;
726
  }
727
 
728
+ const xColumn = xAxisSelect.value;
729
+ if (!xColumn) {
730
+ showToast('Please select an X-axis column');
731
+ return;
732
+ }
733
+
734
+ if (selectedYColumns.length === 0) {
735
+ showToast('Please select at least one Y-axis column');
736
+ return;
737
+ }
738
+
739
+ // Extract data for chart
740
+ const labels = [];
741
+ const datasets = [];
742
+
743
+ // Use headers as labels if the selected X column contains non-numeric data
744
+ // Otherwise use the values from that column
745
+ for (let i = 1; i < csvData.length; i++) {
746
+ if (csvData[i].length > xColumn) {
747
+ labels.push(csvData[i][xColumn]);
748
+ }
749
+ }
750
+
751
+ // Create dataset for each selected Y column
752
+ selectedYColumns.forEach((colIndex, datasetIndex) => {
753
+ const dataset = {
754
+ label: csvHeaders[colIndex],
755
+ data: [],
756
+ backgroundColor: currentColors[datasetIndex % currentColors.length],
757
+ borderColor: currentColors[datasetIndex % currentColors.length],
758
+ borderWidth: 1
759
+ };
760
 
761
+ for (let i = 1; i < csvData.length; i++) {
762
+ if (csvData[i].length > colIndex) {
763
+ const value = parseFloat(csvData[i][colIndex]);
764
+ if (!isNaN(value)) {
765
+ dataset.data.push(value);
766
+ } else {
767
+ dataset.data.push(null);
768
+ }
769
  }
770
  }
771
 
772
+ datasets.push(dataset);
773
+ });
774
+
775
+ createChart(labels, datasets);
776
  } else {
777
  // Manual input method
778
+ const points = multiDataPointsContainer.querySelectorAll('.multi-data-point');
779
+ let labels = [];
780
+ let datasets = [
781
+ {
782
+ label: dataset1Label.value,
783
+ data: [],
784
+ backgroundColor: currentColors[0],
785
+ borderColor: currentColors[0],
786
+ borderWidth: 1
787
+ },
788
+ {
789
+ label: dataset2Label.value,
790
+ data: [],
791
+ backgroundColor: currentColors[1],
792
+ borderColor: currentColors[1],
793
+ borderWidth: 1
794
+ }
795
+ ];
796
+
797
  let isValid = true;
798
+ let hasData = false;
799
 
800
  points.forEach(point => {
801
+ const category = point.querySelector('.category-input').value;
802
+ const value1 = point.querySelector('.dataset1-input').value;
803
+ const value2 = point.querySelector('.dataset2-input').value;
804
+
805
+ if (category) {
806
+ labels.push(category);
807
+ hasData = true;
808
+ }
809
 
810
+ if (value1 !== '') {
811
+ const num1 = parseFloat(value1);
812
+ if (!isNaN(num1)) {
813
+ datasets[0].data.push(num1);
814
+ } else {
815
+ datasets[0].data.push(null);
816
+ }
817
+ } else if (category) {
818
+ datasets[0].data.push(null);
819
+ }
820
+
821
+ if (value2 !== '') {
822
+ const num2 = parseFloat(value2);
823
+ if (!isNaN(num2)) {
824
+ datasets[1].data.push(num2);
825
+ } else {
826
+ datasets[1].data.push(null);
827
+ }
828
+ } else if (category) {
829
+ datasets[1].data.push(null);
830
+ }
831
+
832
+ if ((value1 !== '' || value2 !== '') && !category) {
833
  isValid = false;
834
  }
835
  });
836
 
837
+ // Remove datasets that have no data
838
+ datasets = datasets.filter(dataset => dataset.data.some(val => val !== null));
839
+
840
  if (!isValid) {
841
+ showToast('Please fill in category names for data points');
842
  return;
843
  }
844
 
845
+ if (!hasData) {
846
+ showToast('Please add at least one data point with category');
847
  return;
848
  }
849
 
850
+ if (datasets.length === 0) {
851
+ showToast('Please add at least one data value');
852
+ return;
853
+ }
854
+
855
+ createChart(labels, datasets);
856
  }
857
  });
858
 
859
  // Create chart with data
860
+ function createChart(labels, datasets) {
861
  // Update chart title
862
  const title = chartTitle.value || 'Your Chart';
863
  chartTitleDisplay.textContent = title;
864
 
865
+ // Count data points
866
+ const pointCount = datasets.reduce((total, dataset) => {
867
+ return total + dataset.data.filter(val => val !== null).length;
868
+ }, 0);
869
+
870
+ dataCount.textContent = `${pointCount} points`;
871
+ chartInfo.classList.remove('hidden');
872
+
873
  // Destroy existing chart
874
  if (chart) {
875
  chart.destroy();
876
  }
877
 
878
+ // Process labels if they look like dates or numbers
879
+ let xAxisType = 'category';
880
+ let allLabelsAreNumbers = true;
881
+ let allLabelsAreDates = true;
882
+
883
+ labels.forEach(label => {
884
+ const strLabel = String(label);
885
+ // Check if it's a number
886
+ if (isNaN(parseFloat(strLabel))) {
887
+ allLabelsAreNumbers = false;
888
+ }
889
+ // Check if it's a date (very basic check)
890
+ if (!/\d{4}-\d{2}-\d{2}/.test(strLabel) &&
891
+ !/\d{2}\/\d{2}\/\d{4}/.test(strLabel) &&
892
+ isNaN(Date.parse(strLabel))) {
893
+ allLabelsAreDates = false;
894
+ }
895
+ });
896
+
897
+ if (allLabelsAreNumbers && chartType.value !== 'pie' && chartType.value !== 'doughnut') {
898
+ xAxisType = 'linear';
899
+ labels = labels.map(label => parseFloat(label));
900
+ } else if (allLabelsAreDates) {
901
+ xAxisType = 'time';
902
+ // Chart.js with Luxon adapter will handle the date parsing
903
+ }
904
+
905
  // Chart configuration
906
  const chartConfig = {
907
  type: chartType.value,
908
  data: {
909
  labels: labels,
910
+ datasets: datasets
 
 
 
 
 
 
911
  },
912
  options: {
913
  responsive: true,
 
924
  }
925
  },
926
  legend: {
927
+ display: true,
928
+ position: 'bottom'
929
  },
930
  tooltip: {
931
  backgroundColor: 'rgba(0, 0, 0, 0.8)',
 
934
  borderColor: '#333',
935
  borderWidth: 1,
936
  cornerRadius: 6,
937
+ displayColors: true
938
  }
939
  },
940
  scales: {
 
951
  text: yLabel.value || 'Value',
952
  color: '#6b7280'
953
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
954
  }
955
  }
956
  }
957
  };
958
 
959
+ // Configure X-axis based on type
960
+ if (xAxisType === 'linear') {
961
+ chartConfig.options.scales.x = {
962
+ type: 'linear',
963
+ grid: {
964
+ color: 'rgba(0, 0, 0, 0.05)'
965
+ },
966
+ ticks: {
967
+ color: '#6b7280'
968
+ },
969
+ title: {
970
+ display: true,
971
+ text: xLabel.value || 'Value',
972
+ color: '#6b7280'
973
+ }
974
+ };
975
+ } else if (xAxisType === 'time') {
976
+ chartConfig.options.scales.x = {
977
+ type: 'time',
978
+ time: {
979
+ tooltipFormat: 'll'
980
+ },
981
+ grid: {
982
+ color: 'rgba(0, 0, 0, 0.05)'
983
+ },
984
+ ticks: {
985
+ color: '#6b7280'
986
+ },
987
+ title: {
988
+ display: true,
989
+ text: xLabel.value || 'Date',
990
+ color: '#6b7280'
991
+ }
992
+ };
993
+ } else {
994
+ chartConfig.options.scales.x = {
995
+ grid: {
996
+ color: 'rgba(0, 0, 0, 0.05)'
997
+ },
998
+ ticks: {
999
+ color: '#6b7280'
1000
+ },
1001
+ title: {
1002
+ display: true,
1003
+ text: xLabel.value || 'Category',
1004
+ color: '#6b7280'
1005
+ }
1006
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1007
  }
1008
 
1009
+ // Special configurations for different chart types
1010
+ datasets.forEach((dataset, index) => {
1011
+ if (chartType.value === 'bar') {
1012
+ dataset.backgroundColor = currentColors[index % currentColors.length];
1013
+ dataset.borderColor = currentColors[index % currentColors.length];
1014
+ } else if (chartType.value === 'radar') {
1015
+ dataset.backgroundColor = `${currentColors[index % currentColors.length]}44`; // 44 for 25% opacity
1016
+ dataset.borderColor = currentColors[index % currentColors.length];
1017
+ } else if (chartType.value === 'line') {
1018
+ dataset.backgroundColor = `${currentColors[index % currentColors.length]}1A`; // 1A for 10% opacity
1019
+ dataset.borderColor = currentColors[index % currentColors.length];
1020
+ dataset.borderWidth = 3;
1021
+ dataset.pointBackgroundColor = currentColors[index % currentColors.length];
1022
+ dataset.pointBorderColor = '#fff';
1023
+ dataset.pointBorderWidth = 2;
1024
+ dataset.pointRadius = showPoints.checked ? 4 : 0;
1025
+
1026
+ if (!showLines.checked) {
1027
+ dataset.borderWidth = 0;
1028
+ }
1029
+
1030
+ if (!showPoints.checked) {
1031
+ dataset.pointRadius = 0;
1032
+ }
1033
+ } else if (chartType.value === 'scatter') {
1034
+ dataset.pointBackgroundColor = currentColors[index % currentColors.length];
1035
+ dataset.pointBorderColor = '#fff';
1036
+ dataset.pointBorderWidth = 1;
1037
+ dataset.pointRadius = 6;
1038
+ chartConfig.options.scales.x.title.display = true;
1039
+
1040
+ // Add trendline if requested
1041
+ if (showTrendline.checked) {
1042
+ // Calculate linear regression
1043
+ const data = dataset.data;
1044
+ let sumX = 0, sumY = 0, sumXY = 0, sumXX = 0;
1045
+ let validPoints = 0;
1046
+
1047
+ for (let i = 0; i < data.length; i++) {
1048
+ if (data[i] !== null) {
1049
+ sumX += i;
1050
+ sumY += data[i];
1051
+ sumXY += i * data[i];
1052
+ sumXX += i * i;
1053
+ validPoints++;
1054
+ }
1055
+ }
1056
+
1057
+ if (validPoints > 1) {
1058
+ const slope = (validPoints * sumXY - sumX * sumY) / (validPoints * sumXX - sumX * sumX);
1059
+ const intercept = (sumY - slope * sumX) / validPoints;
1060
+
1061
+ // Add trendline dataset
1062
+ const trendData = [];
1063
+ const startX = 0;
1064
+ const endX = data.length - 1;
1065
+ trendData.push({x: startX, y: slope * startX + intercept});
1066
+ trendData.push({x: endX, y: slope * endX + intercept});
1067
+
1068
+ chartConfig.data.datasets.push({
1069
+ label: `${dataset.label} Trendline`,
1070
+ data: trendData,
1071
+ showLine: true,
1072
+ pointRadius: 0,
1073
+ borderColor: dataset.borderColor,
1074
+ borderDash: [5, 5],
1075
+ borderWidth: 2
1076
+ });
1077
+ }
1078
+ }
1079
+
1080
+ // For scatter, we need to format data as {x, y} objects
1081
+ const xLabels = chartConfig.data.labels;
1082
+ const scatterData = [];
1083
+ for (let i = 0; i < data.length; i++) {
1084
+ if (data[i] !== null) {
1085
+ let xVal;
1086
+ // If labels are numbers, use them as X values
1087
+ if (!isNaN(parseFloat(xLabels[i]))) {
1088
+ xVal = parseFloat(xLabels[i]);
1089
+ } else {
1090
+ xVal = i; // Otherwise use index
1091
+ }
1092
+ scatterData.push({x: xVal, y: data[i]});
1093
+ }
1094
+ }
1095
+
1096
+ // Clear the original data and replace with scatter data
1097
+ dataset.data = scatterData;
1098
+
1099
+ // Update config for scatter
1100
+ if (xAxisType === 'linear' && !isNaN(parseFloat(xLabels[0]))) {
1101
+ chartConfig.options.scales.x.type = 'linear';
1102
+ chartConfig.options.scales.x.title.text = xLabel.value || 'X Value';
1103
+ } else {
1104
+ chartConfig.options.scales.x.type = 'linear';
1105
+ chartConfig.options.scales.x.title.text = xLabel.value || 'Index';
1106
+ }
1107
+
1108
+ chartConfig.options.scales.y.title.text = yLabel.value || 'Y Value';
1109
+ } else if (chartType.value === 'bubble') {
1110
+ // Convert data to bubble format
1111
+ const bubbleData = [];
1112
+ for (let i = 0; i < data.length; i++) {
1113
+ if (data[i] !== null) {
1114
+ bubbleData.push({
1115
+ x: i,
1116
+ y: data[i],
1117
+ r: Math.abs(data[i]) * 2 + 5
1118
+ });
1119
+ }
1120
+ }
1121
+ dataset.data = bubbleData;
1122
+ chartConfig.options.scales.x.min = 0;
1123
+ chartConfig.options.scales.x.max = Math.max(data.length - 1, 1);
1124
+ }
1125
+ });
1126
+
1127
  // Create new chart
1128
  chart = new Chart(graph, chartConfig);
1129
 
1130
  // Hide empty state
1131
  emptyState.classList.add('hidden');
1132
 
1133
+ // Add animation class to chart
1134
+ graph.classList.add('chart-update');
1135
+ setTimeout(() => {
1136
+ graph.classList.remove('chart-update');
1137
+ }, 300);
1138
+
1139
  // Show success message
1140
  showToast('Chart generated successfully!');
1141
  }
 
1159
  showToast(`Chart downloaded as ${filename}`);
1160
  });
1161
 
1162
+ // Export data
1163
+ exportDataBtn.addEventListener('click', function() {
1164
+ if (!chart) {
1165
+ showToast('No data to export');
1166
+ return;
1167
+ }
1168
+
1169
+ // Create CSV content from chart data
1170
+ let csvContent = "data:text/csv;charset=utf-8,";
1171
+
1172
+ // Add headers
1173
+ const datasets = chart.data.datasets;
1174
+ const labels = chart.data.labels;
1175
+
1176
+ // Build header row
1177
+ let headerRow = "X";
1178
+ datasets.forEach(dataset => {
1179
+ headerRow += `,${dataset.label}`;
1180
+ });
1181
+ csvContent += headerRow + "\n";
1182
+
1183
+ // Build data rows
1184
+ for (let i = 0; i < labels.length; i++) {
1185
+ let row = labels[i];
1186
+ datasets.forEach(dataset => {
1187
+ // Handle different data formats (simple values, objects with x/y, etc.)
1188
+ let value = '';
1189
+ if (Array.isArray(dataset.data)) {
1190
+ const dataPoint = dataset.data[i];
1191
+ if (dataPoint !== null && typeof dataPoint === 'object' && dataPoint.y !== undefined) {
1192
+ value = dataPoint.y;
1193
+ } else if (dataPoint !== null) {
1194
+ value = dataPoint;
1195
+ }
1196
+ }
1197
+ row += `,${value}`;
1198
+ });
1199
+ csvContent += row + "\n";
1200
+ }
1201
+
1202
+ // Create download link
1203
+ const encodedUri = encodeURI(csvContent);
1204
+ const link = document.createElement("a");
1205
+ link.setAttribute("href", encodedUri);
1206
+ link.setAttribute("download", "chart_data.csv");
1207
+ document.body.appendChild(link);
1208
+ link.click();
1209
+ document.body.removeChild(link);
1210
+
1211
+ showToast('Data exported successfully!');
1212
+ });
1213
+
1214
  // Fullscreen functionality
1215
  fullscreenBtn.addEventListener('click', function() {
1216
  fullscreenModal.classList.remove('hidden');
 
1258
  }
1259
 
1260
  // Initialize with empty data point if none exists
1261
+ if (multiDataPointsContainer.children.length === 0) {
1262
+ addMultiPointBtn.click();
1263
  }
1264
  });
1265
  </script>
prompts.txt CHANGED
@@ -1 +1,2 @@
1
- tolong buatkan grafik creator yang memiliki beberapa fitur pengaturan dan bentuk grafik yang bisa di pilih. baik dengan cara upload data atau dengan inputing data
 
 
1
+ tolong buatkan grafik creator yang memiliki beberapa fitur pengaturan dan bentuk grafik yang bisa di pilih. baik dengan cara upload data atau dengan inputing data
2
+ pastikan data x dan y dapat di input dengan banyak header