diff --git a/assets/js/scripts.js b/assets/js/scripts.js index 0271e30261457c12632892ecf7c983d3d074c46c..679d7b96d1f30c97993be14d442e9e4ac807b0f1 100644 --- a/assets/js/scripts.js +++ b/assets/js/scripts.js @@ -1,9 +1,12 @@ + + // Global variables let currentDate = new Date(); let globalData = []; let satelliteRelationships = null; // Store relationship data let autoLoadEnabled = false; // Flag to control automatic data loading //const apiBasePath = 'cgi-bin'; +//const const apiBasePath = 'assets/python'; $(document).ready(function(){ @@ -371,9 +374,10 @@ function updateInstrumentDropdown(data) { // Optimized API response handling with memory management function fetchDataForDay(dateObj){ - let year = dateObj.getFullYear(); - let month = (dateObj.getMonth() + 1).toString().padStart(2, "0"); - let day = dateObj.getDate().toString().padStart(2, "0"); + // Use UTC date methods instead of local time methods + let year = dateObj.getUTCFullYear(); + let month = (dateObj.getUTCMonth() + 1).toString().padStart(2, "0"); + let day = dateObj.getUTCDate().toString().padStart(2, "0"); let dateStr = `${year}-${month}-${day}`; // Get selected options @@ -381,7 +385,7 @@ function fetchDataForDay(dateObj){ let selectedCoverage = $("#coverage_select").val(); let selectedInstrument = $("#instrument_select").val(); - // Use fixed interval (5T) + // Use fixed interval (5T) - explicitly specify it's for UTC time let query = `start_date=${dateStr}&end_date=${dateStr}&start_hour=00:00&end_hour=23:59`; if (selectedSatellite) { query += `&satellite_id=${selectedSatellite}`; @@ -420,6 +424,32 @@ function fetchDataForDay(dateObj){ try { if (response.data && response.data.length > 0) { + // Create UTC day boundaries for filtering + const currentUTCDay = new Date(Date.UTC( + dateObj.getUTCFullYear(), + dateObj.getUTCMonth(), + dateObj.getUTCDate() + )); + + const nextUTCDay = new Date(currentUTCDay); + nextUTCDay.setUTCDate(nextUTCDay.getUTCDate() + 1); + + // Pre-process data to ensure dates are properly parsed as UTC + response.data = response.data.map(item => { + if (item.start_time) { + item.start_time = new Date(item.start_time); + } + return item; + }); + + // Filter data to only include entries from the selected UTC day + response.data = response.data.filter(item => + item.start_time >= currentUTCDay && + item.start_time < nextUTCDay + ); + + console.log(`Filtered to ${response.data.length} records within UTC day: ${dateStr}`); + // Store in global variable for reuse without refetching window.globalData = response.data; @@ -444,7 +474,8 @@ function fetchDataForDay(dateObj){ } if (filteredData.length > 0) { - displayData(filteredData); + // Pass UTC day boundaries to displayData + displayData(filteredData, currentUTCDay, nextUTCDay); } else { showError("No data available after applying filters."); } @@ -553,11 +584,23 @@ function updateInstrumentFromRelationships() { } // Fetch satellite list and select first one by default -// Function 2: fetchSatellitesForDay - Complete updated function +// Fetch satellite list and maintain current selections when changing days function fetchSatellitesForDay(dateObj) { - let year = dateObj.getFullYear(); - let month = (dateObj.getMonth() + 1).toString().padStart(2, "0"); - let day = dateObj.getDate().toString().padStart(2, "0"); + // Store current selections before updating the dropdowns + const currentSatelliteId = $("#satellite_id").val(); + const currentCoverage = $("#coverage_select").val(); + const currentInstrument = $("#instrument_select").val(); + + // Log for debugging + console.log("Stored current selections:", { + satellite: currentSatelliteId, + coverage: currentCoverage, + instrument: currentInstrument + }); + + let year = dateObj.getUTCFullYear(); + let month = (dateObj.getUTCMonth() + 1).toString().padStart(2, "0"); + let day = dateObj.getUTCDate().toString().padStart(2, "0"); let dateStr = `${year}-${month}-${day}`; $.ajax({ @@ -570,25 +613,63 @@ function fetchSatellitesForDay(dateObj) { $select.append('<option value="">All Satellites</option>'); if(response.satellites && response.satellites.length > 0) { + let satelliteFound = false; + response.satellites .sort((a, b) => a.id.localeCompare(b.id)) - .forEach((sat, index) => { + .forEach((sat) => { const statusSymbol = sat.fileExists ? '✓' : '✗'; $select.append(`<option value="${sat.id}">${sat.id} ${statusSymbol}</option>`); - // Select first satellite by default - if (index === 0) { - $select.val(sat.id); + // Check if our previously selected satellite is in the new list + if (currentSatelliteId && sat.id === currentSatelliteId) { + satelliteFound = true; } }); // Update status display updateSatelliteStatus(response.satellites, response.baseDir); + // Try to select the previously selected satellite if it exists in the new day + if (satelliteFound) { + $select.val(currentSatelliteId); + } else if (currentSatelliteId) { + console.log(`Previously selected satellite ${currentSatelliteId} not found in new day`); + // If the specific satellite isn't available, see if we should select "All Satellites" + if (currentSatelliteId === "") { + $select.val(""); + } else { + // Select the first satellite as fallback + $select.val(response.satellites[0].id); + } + } else { + // Default: select first satellite if no previous selection + $select.val(response.satellites[0].id); + } + // Update coverage dropdown based on relationships updateCoverageFromRelationships(); + + // After coverage is updated, try to restore the previous coverage selection + if (currentCoverage) { + // Check if the coverage option exists in the new dropdown + if ($("#coverage_select option[value='" + currentCoverage + "']").length) { + $("#coverage_select").val(currentCoverage); + } + + // Update instrument dropdown based on the new (or restored) coverage + updateInstrumentFromRelationships(); + + // After instrument is updated, try to restore the previous instrument selection + if (currentInstrument) { + // Check if the instrument option exists in the new dropdown + if ($("#instrument_select option[value='" + currentInstrument + "']").length) { + $("#instrument_select").val(currentInstrument); + } + } + } - // Fetch data for the first satellite only if autoLoadEnabled is true + // Fetch data for the selected options if (autoLoadEnabled) { fetchDataForDay(dateObj); } else { @@ -1086,16 +1167,31 @@ function downloadCurrentDataAsCSV() { } // Display chart and data table with optimizations for large datasets -function displayData(data) { +function displayData(data, currentUTCDay, nextUTCDay) { if(!data || data.length === 0) { showError("No data available to display."); return; } try { + // Create UTC day boundaries if not provided + if (!currentUTCDay) { + currentUTCDay = new Date(Date.UTC( + currentDate.getUTCFullYear(), + currentDate.getUTCMonth(), + currentDate.getUTCDate() + )); + + nextUTCDay = new Date(currentUTCDay); + nextUTCDay.setUTCDate(nextUTCDay.getUTCDate() + 1); + } + // Debug logging console.log("Display data called with", data.length, "records"); + console.log("UTC day bounds:", currentUTCDay.toISOString(), "to", nextUTCDay.toISOString()); + addDownloadButton(); + // Check if dataset is too large and warn user const DATA_WARNING_THRESHOLD = 5000; if (data.length > DATA_WARNING_THRESHOLD) { @@ -1124,13 +1220,21 @@ function displayData(data) { start_time: ensureDate(item.start_time) })); - // Remove any invalid entries to prevent plotting errors + // Double-check that all data is within the UTC day boundaries data = data.filter(item => + item.start_time >= currentUTCDay && + item.start_time < nextUTCDay && !isNaN(item.latency) && - item.start_time instanceof Date && !isNaN(item.start_time.getTime()) ); + console.log(`Final data points after strict UTC filtering: ${data.length}`); + + if (data.length === 0) { + showError("No data available within the selected UTC day."); + return; + } + // Sort data by time data.sort((a, b) => a.start_time - b.start_time); @@ -1151,13 +1255,13 @@ function displayData(data) { sourceData.x.push(item.start_time); sourceData.y.push(item.latency); } - - // Convert Map to array of traces + + // Convert Map to array of traces with explicit UTC time handling const ingestSources = Array.from(ingestSourceMap.keys()); const traces = ingestSources.map(source => { const sourceData = ingestSourceMap.get(source); return { - x: sourceData.x, + x: sourceData.x.map(date => date.toISOString()), // Force UTC interpretation with ISO string y: sourceData.y, type: 'scattergl', // Use WebGL for better performance with large datasets mode: 'lines+markers', @@ -1254,9 +1358,14 @@ function displayData(data) { if (time > maxTime) maxTime = time; } + // Format the date to display in the chart title + const formattedDate = currentUTCDay.toISOString().split('T')[0]; + const dateStr = formattedDate; + + // Create comprehensive layout with proper UTC handling let layout = { title: { - text: `Latency Data for ${selectedSatellite} - ${selectedInstrument}`, + text: `Latency Data for ${selectedSatellite} - ${selectedInstrument} (${formattedDate} UTC)`, font: { size: 24 } }, xaxis: { @@ -1264,7 +1373,24 @@ function displayData(data) { tickangle: -45, gridcolor: '#eee', type: 'date', - // Add range selector buttons for time selection + // Force UTC interpretation for all dates + hoverformat: '%Y-%m-%d %H:%M:%S UTC', + tickformat: '%H:%M\n%b %d', + // Enable UTC mode for all date handling + tickformatstops: [{ + dtickrange: [null, 86400000], + value: '%H:%M\n%b %d' + }], + // Set exact range to current UTC day boundaries with explicit UTC marker + range: [ + currentUTCDay.toISOString(), + nextUTCDay.toISOString() + ], + // Force fixed range mode + autorange: false, + fixedrange: false, + + // Configure range selector to maintain day boundaries rangeselector: { buttons: [ { @@ -1293,7 +1419,9 @@ function displayData(data) { }, { step: 'all', - label: 'All' + label: 'All Day', + // When "All" is clicked, return to the full day view + stepmode: 'todate' } ], x: 0.05, @@ -1302,10 +1430,15 @@ function displayData(data) { bordercolor: '#dee2e6', font: { size: 12 } }, - // Add range slider for manual time selection + + // Modify rangeslider to respect boundaries rangeslider: { visible: true, - thickness: 0.05 + thickness: 0.05, + range: [ + currentUTCDay.toISOString(), + nextUTCDay.toISOString() + ] } }, yaxis: { @@ -1328,11 +1461,13 @@ function displayData(data) { yanchor: 'top' }, height: 650, // Increased height to accommodate the range slider - // Performance optimization - uirevision: 'true', // Maintains zoom level on updates + + // Critical for maintaining our fixed range settings + uirevision: dateStr, // Use the date string to reset when day changes hovermode: 'closest' }; + // Explicitly set Plotly configuration for UTC handling let config = { responsive: true, displayModeBar: true, @@ -1340,8 +1475,20 @@ function displayData(data) { modeBarButtonsToRemove: ['lasso2d', 'select2d'], toImageButtonOptions: { format: 'png', - filename: `latency_${selectedSatellite}_${new Date().toISOString().split('T')[0]}` - } + filename: `latency_${selectedSatellite}_${formattedDate}_UTC` + }, + // Force UTC time interpretation + locales: { + 'en-US': { + date: '%Y-%m-%d', + hours: '%H:%M', + months: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + shortDays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] + } + }, + locale: 'en-US' }; // Use newPlot only on first render, otherwise update @@ -1350,7 +1497,7 @@ function displayData(data) { } else { Plotly.newPlot('chart', traces, layout, config); } - + // Build data table more efficiently by calculating HTML once // Don't show all data in the table, limit to a reasonable number const MAX_TABLE_ROWS = 1000; @@ -1360,6 +1507,7 @@ function displayData(data) { let tableRows = ''; for (const row of tableData) { + // Format time as HH:MM:SS const timeString = row.start_time.toISOString().replace('T', ' ').replace('Z', ''); const instrument = row.instrument === "Not Available" ? "Unknown" : row.instrument; const coverage = row.coverage === "Not Available" ? "Unknown" : row.coverage; @@ -1455,6 +1603,8 @@ function ensureDate(value) { } } + + // Helper function to downsample data for large datasets function downsampleData(data, targetPoints) { if (data.length <= targetPoints) {