Add spike_visualizer/js/app.js
This commit is contained in:
parent
876fe2acc5
commit
60990acc6e
1 changed files with 133 additions and 0 deletions
133
spike_visualizer/js/app.js
Normal file
133
spike_visualizer/js/app.js
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
'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 = `
|
||||
<div class="summary-item">Total Spikes: ${total}</div>
|
||||
<div class="summary-item">Average Reorder Score: ${avgReorder}</div>
|
||||
<div class="summary-item">Average Publish Rate: ${avgPublish}</div>
|
||||
`;
|
||||
}
|
||||
|
||||
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}<br>Score: ${data.reorder_score}<br>Rate: ${data.publish_rate}<br>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);
|
||||
Loading…
Reference in a new issue