aditya-me13 commited on
Commit
66a90a7
Β·
1 Parent(s): e86c10c

FIX: Debug interactive plots and add comprehensive loading indicators

Browse files

πŸ”§ Interactive Plot Fixes:
- Fix HTML generation for proper Plotly rendering
- Add comprehensive debugging and fallback displays
- Install Chromium in Docker for PNG export capability
- Improve HTML template structure and error handling

πŸ’« Loading Indicators:
- Add animated loading spinners to all buttons
- Implement loading states for upload, download, and analysis
- Add progress text updates during processing
- Improve user feedback across all forms

πŸš€ UX Improvements:
- Better error messages and debugging info
- Responsive loading animations with CSS keyframes
- Form submission state management
- Enhanced visual feedback for long operations

Dockerfile CHANGED
@@ -19,6 +19,7 @@ RUN apt-get update && apt-get install -y \
19
  git-lfs \
20
  curl \
21
  wget \
 
22
  && rm -rf /var/lib/apt/lists/*
23
 
24
  # Set environment variables for GDAL
@@ -26,6 +27,10 @@ ENV CPLUS_INCLUDE_PATH=/usr/include/gdal
26
  ENV C_INCLUDE_PATH=/usr/include/gdal
27
  ENV GDAL_DATA=/usr/share/gdal
28
 
 
 
 
 
29
  # Copy requirements first for better caching
30
  COPY requirements.txt .
31
 
 
19
  git-lfs \
20
  curl \
21
  wget \
22
+ chromium-browser \
23
  && rm -rf /var/lib/apt/lists/*
24
 
25
  # Set environment variables for GDAL
 
27
  ENV C_INCLUDE_PATH=/usr/include/gdal
28
  ENV GDAL_DATA=/usr/share/gdal
29
 
30
+ # Set environment variable for Chromium (needed by Kaleido)
31
+ ENV CHROME_BIN=/usr/bin/chromium-browser
32
+ ENV CHROME_PATH=/usr/bin/chromium-browser
33
+
34
  # Copy requirements first for better caching
35
  COPY requirements.txt .
36
 
interactive_plot_generator.py CHANGED
@@ -265,19 +265,31 @@ class InteractiveIndiaMapPlotter:
265
 
266
  if save_plot:
267
  # Generate HTML content for embedding
268
- html_content = pio.to_html(fig, config=config, include_plotlyjs='cdn', div_id='interactive-plot')
 
 
 
 
 
 
269
  result['html_content'] = html_content
270
 
271
  # Save as HTML file
272
  html_path = self._save_html_plot(fig, var_name, display_name, pressure_level, color_theme, time_stamp, config)
273
  result['html_path'] = html_path
274
 
275
- # Save as PNG for fallback
276
  png_path = self._save_png_plot(fig, var_name, display_name, pressure_level, color_theme, time_stamp)
277
  result['png_path'] = png_path
278
  else:
279
  # Just return HTML content for display
280
- html_content = pio.to_html(fig, config=config, include_plotlyjs='cdn')
 
 
 
 
 
 
281
  result['html_content'] = html_content
282
 
283
  return result
 
265
 
266
  if save_plot:
267
  # Generate HTML content for embedding
268
+ html_content = pio.to_html(
269
+ fig,
270
+ config=config,
271
+ include_plotlyjs='cdn',
272
+ div_id='interactive-plot',
273
+ full_html=False
274
+ )
275
  result['html_content'] = html_content
276
 
277
  # Save as HTML file
278
  html_path = self._save_html_plot(fig, var_name, display_name, pressure_level, color_theme, time_stamp, config)
279
  result['html_path'] = html_path
280
 
281
+ # Save as PNG for fallback (only if kaleido works)
282
  png_path = self._save_png_plot(fig, var_name, display_name, pressure_level, color_theme, time_stamp)
283
  result['png_path'] = png_path
284
  else:
285
  # Just return HTML content for display
286
+ html_content = pio.to_html(
287
+ fig,
288
+ config=config,
289
+ include_plotlyjs='cdn',
290
+ div_id='interactive-plot',
291
+ full_html=False
292
+ )
293
  result['html_content'] = html_content
294
 
295
  return result
templates/index.html CHANGED
@@ -57,13 +57,35 @@
57
  cursor: pointer;
58
  font-size: 16px;
59
  font-weight: 600;
60
- transition: background 0.3s;
 
61
  }
62
  .btn:hover { background: #2980b9; }
63
  .btn:disabled {
64
  background: #bdc3c7;
65
  cursor: not-allowed;
66
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  .alert {
68
  padding: 15px;
69
  margin-bottom: 20px;
@@ -250,6 +272,59 @@
250
  </div>
251
 
252
  <script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  document.getElementById('file').addEventListener('change', function() {
254
  const file = this.files[0];
255
  if (file) {
@@ -261,16 +336,17 @@
261
  }
262
  });
263
 
264
- document.querySelector('form[action="/download_date"]').addEventListener('submit', function(e) {
265
- if (!confirm('CAMS data download may take several minutes. Continue?')) {
266
- e.preventDefault();
267
- }
268
- });
269
-
270
  document.getElementById('recentFileForm').addEventListener('submit', function(e) {
271
  e.preventDefault();
272
  const val = document.getElementById('recent_file').value;
273
  if (!val) return;
 
 
 
 
 
 
 
274
  const [filename, filetype] = val.split('|');
275
  // is_download param: true for download, false for upload
276
  const is_download = (filetype === 'download') ? 'true' : 'false';
 
57
  cursor: pointer;
58
  font-size: 16px;
59
  font-weight: 600;
60
+ transition: all 0.3s;
61
+ position: relative;
62
  }
63
  .btn:hover { background: #2980b9; }
64
  .btn:disabled {
65
  background: #bdc3c7;
66
  cursor: not-allowed;
67
  }
68
+ .btn.loading {
69
+ background: #34495e;
70
+ cursor: wait;
71
+ padding-left: 50px;
72
+ }
73
+ .btn.loading::before {
74
+ content: "";
75
+ position: absolute;
76
+ left: 15px;
77
+ top: 50%;
78
+ transform: translateY(-50%);
79
+ width: 16px;
80
+ height: 16px;
81
+ border: 2px solid #ffffff40;
82
+ border-top-color: #ffffff;
83
+ border-radius: 50%;
84
+ animation: spin 1s linear infinite;
85
+ }
86
+ @keyframes spin {
87
+ to { transform: translateY(-50%) rotate(360deg); }
88
+ }
89
  .alert {
90
  padding: 15px;
91
  margin-bottom: 20px;
 
272
  </div>
273
 
274
  <script>
275
+ // Loading indicator functionality
276
+ function addLoadingState(button, originalText) {
277
+ button.classList.add('loading');
278
+ button.disabled = true;
279
+ button.textContent = originalText + ' (Processing...)';
280
+ }
281
+
282
+ function removeLoadingState(button, originalText) {
283
+ button.classList.remove('loading');
284
+ button.disabled = false;
285
+ button.textContent = originalText;
286
+ }
287
+
288
+ // Add loading states to all form submissions
289
+ document.addEventListener('DOMContentLoaded', function() {
290
+ // File upload form
291
+ const uploadForm = document.querySelector('form[action="/upload"]');
292
+ if (uploadForm) {
293
+ uploadForm.addEventListener('submit', function(e) {
294
+ const submitBtn = this.querySelector('button[type="submit"]');
295
+ if (submitBtn) {
296
+ addLoadingState(submitBtn, 'πŸ“ Upload File');
297
+ }
298
+ });
299
+ }
300
+
301
+ // Download date form
302
+ const downloadForm = document.querySelector('form[action="/download_date"]');
303
+ if (downloadForm) {
304
+ downloadForm.addEventListener('submit', function(e) {
305
+ if (!confirm('CAMS data download may take several minutes. Continue?')) {
306
+ e.preventDefault();
307
+ return;
308
+ }
309
+ const submitBtn = this.querySelector('button[type="submit"]');
310
+ if (submitBtn) {
311
+ addLoadingState(submitBtn, 'πŸ“₯ Download CAMS Data');
312
+ }
313
+ });
314
+ }
315
+
316
+ // Cleanup button
317
+ const cleanupBtn = document.querySelector('a[href="/cleanup"]');
318
+ if (cleanupBtn) {
319
+ cleanupBtn.addEventListener('click', function(e) {
320
+ e.preventDefault();
321
+ this.textContent = '🧹 Cleaning...';
322
+ this.style.pointerEvents = 'none';
323
+ window.location.href = this.href;
324
+ });
325
+ }
326
+ });
327
+
328
  document.getElementById('file').addEventListener('change', function() {
329
  const file = this.files[0];
330
  if (file) {
 
336
  }
337
  });
338
 
 
 
 
 
 
 
339
  document.getElementById('recentFileForm').addEventListener('submit', function(e) {
340
  e.preventDefault();
341
  const val = document.getElementById('recent_file').value;
342
  if (!val) return;
343
+
344
+ // Show loading state
345
+ const submitBtn = this.querySelector('button[type="submit"]');
346
+ if (submitBtn) {
347
+ addLoadingState(submitBtn, 'πŸš€ Analyze');
348
+ }
349
+
350
  const [filename, filetype] = val.split('|');
351
  // is_download param: true for download, false for upload
352
  const is_download = (filetype === 'download') ? 'true' : 'false';
templates/interactive_plot.html CHANGED
@@ -205,7 +205,7 @@
205
  </div>
206
 
207
  <div class="plot-container">
208
- <div class="interactive-plot">
209
  {{ plot_html|safe }}
210
  </div>
211
  </div>
@@ -269,15 +269,43 @@
269
  </div>
270
 
271
  <script>
272
- // Additional interactivity enhancements
273
  document.addEventListener('DOMContentLoaded', function() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
  // Add responsive behavior
275
- const plotDiv = document.getElementById('interactive-plot');
276
- if (plotDiv) {
277
- window.addEventListener('resize', function() {
278
  Plotly.Plots.resize(plotDiv);
279
- });
280
- }
281
 
282
  // Add loading indicator for downloads
283
  const downloadButtons = document.querySelectorAll('.btn-download');
@@ -293,6 +321,23 @@
293
  }, 2000);
294
  });
295
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
296
  });
297
  </script>
298
  </body>
 
205
  </div>
206
 
207
  <div class="plot-container">
208
+ <div class="interactive-plot" id="plotly-container">
209
  {{ plot_html|safe }}
210
  </div>
211
  </div>
 
269
  </div>
270
 
271
  <script>
272
+ // Additional interactivity enhancements and debugging
273
  document.addEventListener('DOMContentLoaded', function() {
274
+ console.log('Interactive plot page loaded');
275
+
276
+ // Check if Plotly is loaded
277
+ if (typeof Plotly === 'undefined') {
278
+ console.error('Plotly is not loaded!');
279
+ document.getElementById('plotly-container').innerHTML =
280
+ '<div style="padding: 40px; text-align: center; color: red; border: 2px dashed red; margin: 20px;">' +
281
+ '<h3>⚠️ Plot Loading Error</h3>' +
282
+ '<p>Plotly library failed to load. Please refresh the page or check your internet connection.</p>' +
283
+ '</div>';
284
+ return;
285
+ }
286
+
287
+ // Look for the plotly div
288
+ const plotDiv = document.querySelector('[id^="interactive-plot"]') || document.querySelector('.plotly-graph-div');
289
+
290
+ if (!plotDiv) {
291
+ console.error('No Plotly div found!');
292
+ document.getElementById('plotly-container').innerHTML =
293
+ '<div style="padding: 40px; text-align: center; color: orange; border: 2px dashed orange; margin: 20px;">' +
294
+ '<h3>⚠️ Plot Not Found</h3>' +
295
+ '<p>The interactive plot could not be initialized. The data may be processing.</p>' +
296
+ '<button onclick="location.reload()" style="padding: 10px 20px; background: #3498db; color: white; border: none; border-radius: 5px; cursor: pointer;">πŸ”„ Refresh Page</button>' +
297
+ '</div>';
298
+ return;
299
+ }
300
+
301
+ console.log('Plotly div found:', plotDiv);
302
+
303
  // Add responsive behavior
304
+ window.addEventListener('resize', function() {
305
+ if (plotDiv && typeof Plotly !== 'undefined') {
 
306
  Plotly.Plots.resize(plotDiv);
307
+ }
308
+ });
309
 
310
  // Add loading indicator for downloads
311
  const downloadButtons = document.querySelectorAll('.btn-download');
 
321
  }, 2000);
322
  });
323
  });
324
+
325
+ // Add a small delay then check if plot rendered correctly
326
+ setTimeout(function() {
327
+ const plotlyGraphDiv = document.querySelector('.plotly-graph-div');
328
+ if (plotlyGraphDiv) {
329
+ const svg = plotlyGraphDiv.querySelector('svg');
330
+ if (!svg || svg.children.length === 0) {
331
+ console.warn('Plot may not have rendered correctly');
332
+ // Try to redraw
333
+ if (typeof Plotly !== 'undefined' && plotDiv._fullData) {
334
+ Plotly.redraw(plotDiv);
335
+ }
336
+ } else {
337
+ console.log('Plot rendered successfully');
338
+ }
339
+ }
340
+ }, 1000);
341
  });
342
  </script>
343
  </body>
templates/variables.html CHANGED
@@ -57,15 +57,37 @@
57
  cursor: pointer;
58
  font-size: 16px;
59
  font-weight: 600;
60
- transition: background 0.3s;
61
  text-decoration: none;
62
  display: inline-block;
 
63
  }
64
  .btn:hover { background: #2980b9; }
65
  .btn:disabled {
66
  background: #bdc3c7;
67
  cursor: not-allowed;
68
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  .btn-secondary {
70
  background: #6c757d;
71
  }
@@ -488,10 +510,32 @@
488
  return;
489
  }
490
 
491
- // Show loading message
 
 
 
 
 
 
 
 
 
 
 
 
492
  document.getElementById('loadingMessage').style.display = 'block';
493
- document.getElementById('submitBtn').disabled = true;
494
- document.getElementById('submitBtn').textContent = '⏳ Generating...';
 
 
 
 
 
 
 
 
 
 
495
  });
496
 
497
  // Auto-select first variable if only one available
 
57
  cursor: pointer;
58
  font-size: 16px;
59
  font-weight: 600;
60
+ transition: all 0.3s;
61
  text-decoration: none;
62
  display: inline-block;
63
+ position: relative;
64
  }
65
  .btn:hover { background: #2980b9; }
66
  .btn:disabled {
67
  background: #bdc3c7;
68
  cursor: not-allowed;
69
  }
70
+ .btn.loading {
71
+ background: #34495e;
72
+ cursor: wait;
73
+ padding-left: 50px;
74
+ }
75
+ .btn.loading::before {
76
+ content: "";
77
+ position: absolute;
78
+ left: 15px;
79
+ top: 50%;
80
+ transform: translateY(-50%);
81
+ width: 16px;
82
+ height: 16px;
83
+ border: 2px solid #ffffff40;
84
+ border-top-color: #ffffff;
85
+ border-radius: 50%;
86
+ animation: spin 1s linear infinite;
87
+ }
88
+ @keyframes spin {
89
+ to { transform: translateY(-50%) rotate(360deg); }
90
+ }
91
  .btn-secondary {
92
  background: #6c757d;
93
  }
 
510
  return;
511
  }
512
 
513
+ // Determine which button was clicked
514
+ const submitEvent = e.submitter;
515
+ let loadingText = '⏳ Generating...';
516
+
517
+ if (submitEvent && submitEvent.formAction) {
518
+ if (submitEvent.formAction.includes('visualize_interactive')) {
519
+ loadingText = '🎯 Creating Interactive Plot...';
520
+ } else {
521
+ loadingText = 'πŸ“Š Creating Static Plot...';
522
+ }
523
+ }
524
+
525
+ // Show loading message and update button states
526
  document.getElementById('loadingMessage').style.display = 'block';
527
+
528
+ // Update all buttons to loading state
529
+ const buttons = this.querySelectorAll('button[type="submit"]');
530
+ buttons.forEach(btn => {
531
+ btn.disabled = true;
532
+ btn.classList.add('loading');
533
+ if (btn === submitEvent) {
534
+ btn.textContent = loadingText;
535
+ } else {
536
+ btn.style.opacity = '0.5';
537
+ }
538
+ });
539
  });
540
 
541
  // Auto-select first variable if only one available