'use strict'; async function initApp() { const summaryContainer = document.getElementById('summary'); const chartContainer = document.getElementById('chart'); const filterPanel = document.getElementById('filter-panel'); if (!summaryContainer || !chartContainer || !filterPanel) { console.error('Required DOM elements missing.'); return; } try { const dataset = await fetchSpikeData(); renderSummary(dataset, summaryContainer); renderChart(dataset, chartContainer); bindUIEvents(dataset); } catch (err) { console.error('Failed to initialize app:', err); } } async function fetchSpikeData(params = {}) { const query = new URLSearchParams(params).toString(); const response = await fetch('/api/spikes' + (query ? '?' + query : '')); if (!response.ok) { throw new Error('API request failed with status ' + response.status); } return response.json(); } function renderSummary(data, container) { if (!Array.isArray(data)) return; const total = data.length; const avgReorder = total ? (data.reduce((sum, d) => sum + (d.reorder_score || 0), 0) / total).toFixed(2) : 0; const avgPublish = total ? (data.reduce((sum, d) => sum + (d.publish_rate || 0), 0) / total).toFixed(2) : 0; container.innerHTML = `
Total Spikes: ${total}
Average Reorder Score: ${avgReorder}
Average Publish Rate: ${avgPublish}
`; } function renderChart(data, container) { if (typeof d3 === 'undefined') { container.textContent = 'Visualization library (d3.js) not loaded.'; return; } container.innerHTML = ''; const width = container.clientWidth || 800; const height = 300; const svg = d3.select(container) .append('svg') .attr('width', width) .attr('height', height); const xScale = d3.scaleLinear() .domain(d3.extent(data, d => d.timestamp)) .range([40, width - 20]); const yScale = d3.scaleLinear() .domain([0, d3.max(data, d => d.reorder_score || 0)]) .range([height - 40, 20]); svg.selectAll('circle') .data(data) .enter() .append('circle') .attr('cx', d => xScale(d.timestamp)) .attr('cy', d => yScale(d.reorder_score)) .attr('r', 4) .attr('fill', d => d.migration_flag ? '#d95f02' : '#1b9e77') .on('mouseover', (event, d) => { showTooltip(event, d, container); }) .on('mouseout', () => { hideTooltip(container); }); } function showTooltip(event, data, container) { let tooltip = container.querySelector('.tooltip'); if (!tooltip) { tooltip = document.createElement('div'); tooltip.className = 'tooltip'; container.appendChild(tooltip); } tooltip.innerHTML = `CPU ${data.cpu_id}
Score: ${data.reorder_score}
Rate: ${data.publish_rate}
Migration: ${data.migration_flag}`; tooltip.style.visibility = 'visible'; tooltip.style.left = `${event.offsetX + 10}px`; tooltip.style.top = `${event.offsetY - 20}px`; } function hideTooltip(container) { const tooltip = container.querySelector('.tooltip'); if (tooltip) { tooltip.style.visibility = 'hidden'; } } function bindUIEvents(initialData) { const cpuFilter = document.getElementById('cpu-filter'); const reorderFilter = document.getElementById('reorder-filter'); const timeFilterStart = document.getElementById('start-time'); const timeFilterEnd = document.getElementById('end-time'); const chartContainer = document.getElementById('chart'); const summaryContainer = document.getElementById('summary'); if (!cpuFilter || !chartContainer) return; const updateChart = async () => { const params = {}; if (cpuFilter.value) params.cpu_id = cpuFilter.value; if (timeFilterStart.value) params.start_time = timeFilterStart.value; if (timeFilterEnd.value) params.end_time = timeFilterEnd.value; try { const newData = await fetchSpikeData(params); renderSummary(newData, summaryContainer); renderChart(newData, chartContainer); } catch (err) { console.error('Failed to update chart:', err); } }; [cpuFilter, reorderFilter, timeFilterStart, timeFilterEnd].forEach(el => { if (el) el.addEventListener('change', updateChart); }); } window.addEventListener('load', initApp);