diff --git a/breakdown.js b/breakdown.js new file mode 100644 index 0000000..2e6e177 --- /dev/null +++ b/breakdown.js @@ -0,0 +1,155 @@ +/** + * Simple Pie Chart Renderer + * Lightweight canvas-based pie chart for content breakdown + */ + +function drawPieChart(canvasId, data, options = {}) { + const canvas = document.getElementById(canvasId); + if (!canvas) return; + + const ctx = canvas.getContext('2d'); + const centerX = canvas.width / 2; + const centerY = canvas.height / 2; + const radius = Math.min(centerX, centerY) - 20; + + // Clear canvas + ctx.clearRect(0, 0, canvas.width, canvas.height); + + // Calculate total + const total = data.reduce((sum, item) => sum + item.value, 0); + + // Color palette + const colors = [ + '#7209B7', // Purple (HTML) + '#F72585', // Pink (JS) + '#4361EE', // Blue (CSS) + '#4CC9F0', // Cyan (Images) + '#FFB703', // Orange (Fonts) + '#06D6A0' // Green (Other) + ]; + + let currentAngle = -Math.PI / 2; // Start at top + + data.forEach((item, index) => { + const sliceAngle = (item.value / total) * 2 * Math.PI; + + // Draw slice + ctx.beginPath(); + ctx.moveTo(centerX, centerY); + ctx.arc(centerX, centerY, radius, currentAngle, currentAngle + sliceAngle); + ctx.closePath(); + ctx.fillStyle = colors[index % colors.length]; + ctx.fill(); + + // Draw label + const labelAngle = currentAngle + sliceAngle / 2; + const labelX = centerX + (radius * 0.7) * Math.cos(labelAngle); + const labelY = centerY + (radius * 0.7) * Math.sin(labelAngle); + + ctx.fillStyle = '#ffffff'; + ctx.font = 'bold 12px system-ui'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + const percentage = ((item.value / total) * 100).toFixed(1); + ctx.fillText(`${percentage}%`, labelX, labelY); + + currentAngle += sliceAngle; + }); + + // Draw legend + const legendX = canvas.width - 120; + let legendY = 20; + + data.forEach((item, index) => { + ctx.fillStyle = colors[index % colors.length]; + ctx.fillRect(legendX, legendY, 15, 15); + + ctx.fillStyle = '#333'; + ctx.font = '12px system-ui'; + ctx.textAlign = 'left'; + ctx.fillText(`${item.label}`, legendX + 20, legendY + 11); + + legendY += 25; + }); +} + +/** + * Render content breakdown from HAR data + */ +async function renderContentBreakdown(testId) { + try { + const response = await fetch(`/reports/${testId}.har.json`); + if (!response.ok) throw new Error('HAR data not found'); + + const harData = await response.json(); + const summary = harData.summary; + + // Prepare pie chart data + const chartData = []; + for (const [type, stats] of Object.entries(summary.byType)) { + chartData.push({ + label: type, + value: stats.transfer + }); + } + + // Draw pie chart + drawPieChart('breakdown-chart', chartData); + + // Render statistics table + const statsContainer = document.getElementById('breakdown-stats'); + let tableHtml = ` + + + + + + + + + + + `; + + for (const [type, stats] of Object.entries(summary.byType)) { + tableHtml += ` + + + + + + + `; + } + + tableHtml += ` + + + + + + + +
TypeRequestsSizeTransfer
${type}${stats.count}${formatBytes(stats.size)}${formatBytes(stats.transfer)}
Total${summary.totalRequests}${formatBytes(summary.totalSize)}${formatBytes(summary.totalTransfer)}
+

+ Compression savings: ${summary.compressionSavings}% +

+ `; + + statsContainer.innerHTML = tableHtml; + + // Show the breakdown section + document.getElementById('content-breakdown').style.display = 'block'; + + } catch (error) { + console.error('Failed to load content breakdown:', error); + } +} + +function formatBytes(bytes) { + if (bytes === 0) return '0 B'; + const k = 1024; + const sizes = ['B', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; +} diff --git a/main.js b/main.js index 5dcb62f..222e3e7 100644 --- a/main.js +++ b/main.js @@ -117,6 +117,11 @@ function displayResults(data) { window.open(`/waterfall.html?id=${data.id}`, '_blank'); }; } + + // Load content breakdown + if (typeof renderContentBreakdown === 'function') { + renderContentBreakdown(data.id); + } resultsArea.classList.add('visible');