diff --git a/data_visualization/js/visualization.js b/data_visualization/js/visualization.js new file mode 100644 index 0000000..65bb8c3 --- /dev/null +++ b/data_visualization/js/visualization.js @@ -0,0 +1,145 @@ +"use strict"; + +/** + * Visualization module for replication analysis dashboard. + * Responsible for rendering charts and updating tables dynamically. + */ + +/** + * @typedef {Object} RunComparison + * @property {string} run_id + * @property {Object} metrics + * @property {number[]} metrics.bandwidth + * @property {number[]} metrics.retry_tailp99 + * @property {string[]} segments + */ + +/** + * Render bandwidth chart based on provided run data. + * @param {RunComparison} data + */ +export function renderBandwidthChart(data) { + const container = document.getElementById('bandwidth-chart'); + if (!container) return; + + container.innerHTML = ''; + + const canvas = document.createElement('canvas'); + canvas.setAttribute('aria-label', 'Bandwidth Chart'); + canvas.setAttribute('role', 'img'); + container.appendChild(canvas); + + const ctx = canvas.getContext('2d'); + const bandwidth = data.metrics?.bandwidth ?? []; + if (!bandwidth.length) { + ctx.font = '16px sans-serif'; + ctx.fillText('No bandwidth data available', 10, 30); + return; + } + + const spacing = canvas.width / bandwidth.length || 5; + const maxValue = Math.max(...bandwidth); + const scale = (canvas.height - 20) / maxValue; + + ctx.strokeStyle = '#3366cc'; + ctx.beginPath(); + bandwidth.forEach((val, i) => { + const x = i * spacing + 10; + const y = canvas.height - val * scale - 10; + if (i === 0) ctx.moveTo(x, y); + else ctx.lineTo(x, y); + }); + ctx.stroke(); +} + +/** + * Render retry_tailp99 chart as a trend visualization. + * @param {RunComparison} data + */ +export function renderRetryTailChart(data) { + const container = document.getElementById('retry-tailp99-chart'); + if (!container) return; + + container.innerHTML = ''; + + const canvas = document.createElement('canvas'); + canvas.setAttribute('aria-label', 'Retry Tailp99 Trend'); + canvas.setAttribute('role', 'img'); + container.appendChild(canvas); + + const ctx = canvas.getContext('2d'); + const retries = data.metrics?.retry_tailp99 ?? []; + if (!retries.length) { + ctx.font = '16px sans-serif'; + ctx.fillText('No retry_tailp99 data available', 10, 30); + return; + } + + const spacing = canvas.width / retries.length || 5; + const maxValue = Math.max(...retries); + const scale = (canvas.height - 20) / maxValue; + + ctx.strokeStyle = '#cc3333'; + ctx.beginPath(); + retries.forEach((val, i) => { + const x = i * spacing + 10; + const y = canvas.height - val * scale - 10; + if (i === 0) ctx.moveTo(x, y); + else ctx.lineTo(x, y); + }); + ctx.stroke(); +} + +/** + * Update results table with latest run metrics. + * @param {RunComparison} data + */ +export function updateResultsTable(data) { + const table = document.getElementById('results-table'); + if (!table) return; + + table.innerHTML = ''; + + const headerRow = document.createElement('tr'); + ['Segment', 'Bandwidth', 'Retry Tail p99'].forEach(h => { + const th = document.createElement('th'); + th.textContent = h; + headerRow.appendChild(th); + }); + table.appendChild(headerRow); + + const {segments = [], metrics: {bandwidth = [], retry_tailp99 = []} = {}} = data; + const len = Math.max(segments.length, bandwidth.length, retry_tailp99.length); + + for (let i = 0; i < len; i++) { + const row = document.createElement('tr'); + const seg = document.createElement('td'); + seg.textContent = segments[i] ?? '-'; + + const bw = document.createElement('td'); + bw.textContent = bandwidth[i]?.toFixed(2) ?? '-'; + + const rt = document.createElement('td'); + rt.textContent = retry_tailp99[i]?.toFixed(2) ?? '-'; + + row.appendChild(seg); + row.appendChild(bw); + row.appendChild(rt); + table.appendChild(row); + } +} + +/** + * Ensure responsiveness on resize. + */ +window.addEventListener('resize', () => { + const dataAttr = document.body.dataset.runComparison; + if (!dataAttr) return; + try { + const data = JSON.parse(dataAttr); + renderBandwidthChart(data); + renderRetryTailChart(data); + } catch (err) { + console.error('Failed to re-render charts on resize:', err); + } +}); \ No newline at end of file