max_outlier_analysis/max_outlier_visualization/js/ui.js

168 lines
No EOL
4 KiB
JavaScript

"use strict";
/**
* UI Logic for Max-Outlier Visualization
* Handles rendering of table, chart, and filter interactions.
* Relies on api.js for data fetching.
*/
import { fetchMaxOutlierResults } from './api.js';
const ui = (() => {
const tableContainer = document.getElementById('outlier-table');
const chartContainer = document.getElementById('outlier-chart');
const filters = document.querySelectorAll('.filter-input');
/**
* Render results table from API data.
* @param {Array<Object>} data
*/
const renderTable = (data) => {
if (!tableContainer) return;
const table = document.createElement('table');
table.className = 'outlier__table';
const header = document.createElement('thead');
header.innerHTML = `
<tr>
<th>Run ID</th>
<th>Parallelism</th>
<th>Stratum</th>
<th>Δt</th>
<th>p95</th>
<th>p99</th>
<th>Retry Status</th>
</tr>
`;
table.appendChild(header);
const body = document.createElement('tbody');
data.forEach((row) => {
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${row.run_id ?? '-'}</td>
<td>${row.parallelism ?? '-'}</td>
<td>${row.stratum ?? '-'}</td>
<td>${row.delta_t ?? '-'}</td>
<td>${row.p95 ?? '-'}</td>
<td>${row.p99 ?? '-'}</td>
<td>${row.retry_status ?? '-'}</td>
`;
body.appendChild(tr);
});
table.appendChild(body);
tableContainer.innerHTML = '';
tableContainer.appendChild(table);
};
/**
* Render chart for p95/p99 visualization.
* @param {Array<Object>} data
*/
const renderChart = (data) => {
if (!chartContainer) return;
const ctx = chartContainer.getContext('2d');
if (!ctx) return;
const labels = data.map((d) => `${d.run_id}`);
const p95Values = data.map((d) => d.p95);
const p99Values = data.map((d) => d.p99);
if (chartContainer.chartInstance) {
chartContainer.chartInstance.destroy();
}
// Basic chart setup using Chart.js style interface if loaded
chartContainer.chartInstance = new Chart(ctx, {
type: 'line',
data: {
labels,
datasets: [
{
label: 'p95',
data: p95Values,
borderColor: 'var(--primary)',
backgroundColor: 'rgba(0, 128, 255, 0.2)',
fill: true,
tension: 0.2,
},
{
label: 'p99',
data: p99Values,
borderColor: 'var(--accent)',
backgroundColor: 'rgba(255, 64, 64, 0.2)',
fill: true,
tension: 0.2,
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
title: {
display: true,
text: 'Run ID'
}
},
y: {
title: {
display: true,
text: 'Metric Value'
}
}
},
plugins: {
legend: {
position: 'top'
},
tooltip: {
mode: 'index',
intersect: false
}
}
}
});
};
/**
* Handle filter input change, fetch new data and re-render views.
* @param {Event} e
*/
const handleFilterChange = async (e) => {
e?.preventDefault();
const params = {};
filters.forEach((input) => {
if (input.value.trim()) {
params[input.name] = input.value.trim();
}
});
try {
const results = await fetchMaxOutlierResults(params);
renderTable(results);
renderChart(results);
} catch (err) {
console.error('Failed to update data:', err);
}
};
// Binding filter events on page load
const bindFilterEvents = () => {
filters.forEach((input) => {
input.addEventListener('change', handleFilterChange);
});
};
// Public API
return {
renderTable,
renderChart,
handleFilterChange,
bindFilterEvents
};
})();
export default ui;