diff --git a/templates/index.html b/templates/index.html index 85338c0..e4efde6 100644 --- a/templates/index.html +++ b/templates/index.html @@ -385,13 +385,12 @@ -
- +
-

Current CPU Distribution

+

CPU Distribution (Current vs Projected)

- +
@@ -405,26 +404,6 @@
- - -
-
-

Projected CPU Distribution

-
- -
-
-
-
- Mean: 0% -
-
-
- ±0.5σ: 0% -
-
-
-
@@ -490,8 +469,7 @@ document.getElementById('previewStrategy').textContent = option.dataset.strategy || 'Balanced'; }); - let cpuHostChart = null; - let cpuProjectedChart = null; + let cpuDistributionChart = null; function setStat(key, text) { var el = document.querySelector('[data-stats="' + key + '"]'); @@ -660,29 +638,20 @@ migrationCount.textContent = `${data.migrations.length} action${data.migrations.length !== 1 ? 's' : ''}`; } - // Update CPU charts + // Update CPU chart (combined current vs projected) function updateCPUCharts(auditId) { const data = auditData[auditId]; if (!data || !data.hostData) return; - const currentCtx = document.getElementById('cpuHostChart').getContext('2d'); - const projectedCtx = document.getElementById('cpuProjectedChart').getContext('2d'); + const ctx = document.getElementById('cpuDistributionChart').getContext('2d'); - // Calculate statistics const currentStats = calculateStats(data.hostData.current); - const projectedStats = calculateStats(data.hostData.projected); - // Update stats displays document.getElementById('currentCpuMean').textContent = currentStats.mean.toFixed(1); - document.getElementById('projectedCpuMean').textContent = projectedStats.mean.toFixed(1); - document.getElementById('projectedCpuStd').textContent = (currentStats.std * 0.5).toFixed(1); document.getElementById('currentCpuStd').textContent = (currentStats.std * 0.5).toFixed(1); - // Destroy existing charts - if (cpuHostChart) cpuHostChart.destroy(); - if (cpuProjectedChart) cpuProjectedChart.destroy(); + if (cpuDistributionChart) cpuDistributionChart.destroy(); - // Chart colors const colors = { primary: getCSSVar('--color-primary'), secondary: getCSSVar('--color-secondary'), @@ -694,28 +663,90 @@ error: getCSSVar('--color-error') }; - // Create current CPU chart - cpuHostChart = new Chart(currentCtx, { + cpuDistributionChart = new Chart(ctx, { type: 'bar', data: { labels: data.hostData.labels, - datasets: [{ - label: 'CPU %', - data: data.hostData.current, - backgroundColor: colors.info + '40', - borderColor: colors.info, - borderWidth: 1, - borderRadius: 3 - }] + datasets: [ + { + label: 'Current', + data: data.hostData.current.slice(), + backgroundColor: colors.info + '40', + borderColor: colors.info, + borderWidth: 1, + borderRadius: 3 + }, + { + label: 'Projected', + data: data.hostData.projected.slice(), + backgroundColor: colors.warning + '40', + borderColor: colors.warning, + borderWidth: 1, + borderRadius: 3 + } + ] }, options: { responsive: true, maintainAspectRatio: false, + animation: { + onComplete: function() { + var chart = this; + if (typeof chart.getDatasetMeta !== 'function') chart = chart.chart; + if (!chart || chart._hidingDataset === undefined) return; + var i = chart._hidingDataset; + chart.getDatasetMeta(i).hidden = true; + chart.data.datasets[i].data = chart._cpuOriginalData[i].slice(); + delete chart._hidingDataset; + chart.update('none'); + } + }, plugins: { - legend: { display: false }, + legend: { + display: true, + position: 'top', + align: 'center', + onClick: function(e, legendItem, legend) { + const i = legendItem.datasetIndex; + const chart = legend.chart; + const len = chart.data.labels.length; + if (chart.isDatasetVisible(i)) { + chart._hidingDataset = i; + chart.data.datasets[i].data = Array(len).fill(0); + chart.update(); + } else { + chart.data.datasets[i].data = Array(len).fill(0); + chart.show(i); + chart.update('none'); + chart.data.datasets[i].data = chart._cpuOriginalData[i].slice(); + chart.update(); + } + }, + labels: { + usePointStyle: true, + pointStyle: 'rect', + boxWidth: 14, + boxHeight: 14, + padding: 12, + color: 'inherit', + generateLabels: function(chart) { + const datasets = chart.data.datasets; + return datasets.map(function(ds, i) { + return { + text: ds.label, + fillStyle: ds.borderColor, + strokeStyle: ds.borderColor, + lineWidth: 1, + hidden: !chart.isDatasetVisible(i), + datasetIndex: i + }; + }); + } + } + }, tooltip: { callbacks: { - label: (ctx) => `${Number(ctx.parsed.y).toFixed(2)}% CPU` + label: (ctx) => `${ctx.dataset.label}: ${Number(ctx.parsed.y).toFixed(2)}% CPU` } }, annotation: { @@ -747,80 +778,6 @@ } } }, - scales: { - y: { - beginAtZero: true, - max: 100, - grid: { - drawBorder: false, - }, - ticks: { - callback: value => value + '%' - } - }, - x: { - grid: { display: false }, - ticks: { - maxRotation: 45 - } - } - } - } - }); - - // Create projected CPU chart - cpuProjectedChart = new Chart(projectedCtx, { - type: 'bar', - data: { - labels: data.hostData.labels, - datasets: [{ - label: 'Projected CPU %', - data: data.hostData.projected, - backgroundColor: colors.warning + '40', - borderColor: colors.warning, - borderWidth: 1, - borderRadius: 3 - }] - }, - options: { - responsive: true, - maintainAspectRatio: false, - plugins: { - legend: { display: false }, - tooltip: { - callbacks: { - label: (ctx) => `${Number(ctx.parsed.y).toFixed(2)}% CPU` - } - }, - annotation: { - annotations: { - MeanLine: { - type: 'line', - yMin: projectedStats.mean.toFixed(1), - yMax: projectedStats.mean.toFixed(1), - borderColor: colors.success, - borderWidth: 2, - borderDash: [] - }, - upperStdLine: { - type: 'line', - yMin: (currentStats.mean + currentStats.std * 0.5).toFixed(1), - yMax: (currentStats.mean + currentStats.std * 0.5).toFixed(1), - borderColor: colors.error, - borderWidth: 1, - borderDash: [5, 5] - }, - lowerStdLine: { - type: 'line', - yMin: currentStats.mean > currentStats.std * 0.5 ? (currentStats.mean - currentStats.std * 0.5).toFixed(1) : 0, - yMax: currentStats.mean > currentStats.std * 0.5 ? (currentStats.mean - currentStats.std * 0.5).toFixed(1) : 0, - borderColor: colors.error, - borderWidth: 1, - borderDash: [5, 5] - } - } - } - }, scales: { y: { beginAtZero: true, @@ -833,12 +790,18 @@ x: { grid: { display: false }, ticks: { - maxRotation: 45 - } + display: false + }, + barPercentage: 1, + categoryPercentage: 0.85 } } } }); + cpuDistributionChart._cpuOriginalData = [ + data.hostData.current.slice(), + data.hostData.projected.slice() + ]; } // Utility functions