/** * Interactive Analytics Dashboard & Charts (Chart.js) */ class RLMetricsDashboard { constructor() { this.rewardChart = null; this.lossChart = null; this.historyData = this.generatePrecomputedTrainingData(); this.initCharts(); } // Precomputed realistic training data for 50,000 steps of PPO Pusher-v5 generatePrecomputedTrainingData() { const totalSteps = 50000; const interval = 1000; const points = totalSteps / interval; const timesteps = []; const rawRewards = []; const maRewards = []; const policyLosses = []; const valueLosses = []; const successRates = []; let currentReward = -85.0; let ma = -85.0; for (let i = 0; i <= points; i++) { const step = i * interval; timesteps.push(step); // Logarithmic / Sigmoidal learning curve with noise const progress = i / points; const targetReward = -85.0 + 80.0 * Math.pow(progress, 0.7); const noise = (Math.random() - 0.48) * (20.0 * (1.0 - progress * 0.6)); const stepReward = targetReward + noise; rawRewards.push(parseFloat(stepReward.toFixed(2))); ma = ma * 0.85 + stepReward * 0.15; maRewards.push(parseFloat(ma.toFixed(2))); // Losses const pLoss = (Math.sin(i * 0.5) * 0.02 + 0.03) * Math.exp(-progress * 2.0); const vLoss = (25.0 * Math.exp(-progress * 3.0) + Math.random() * 2.0 + 0.5); policyLosses.push(parseFloat(pLoss.toFixed(4))); valueLosses.push(parseFloat(vLoss.toFixed(2))); // Success Rate % const sRate = Math.min(98, Math.max(0, Math.round(Math.pow(progress, 1.5) * 95 + (Math.random() * 6 - 3)))); successRates.push(sRate); } return { timesteps, rawRewards, maRewards, policyLosses, valueLosses, successRates }; } initCharts() { const commonChartOptions = { responsive: true, maintainAspectRatio: false, interaction: { mode: 'index', intersect: false, }, plugins: { legend: { labels: { color: '#94a3b8', font: { family: 'Outfit', size: 11 } } }, tooltip: { backgroundColor: 'rgba(15, 23, 42, 0.9)', titleColor: '#00f2fe', bodyColor: '#f8fafc', borderColor: 'rgba(0, 242, 254, 0.3)', borderWidth: 1, padding: 10, titleFont: { family: 'JetBrains Mono', weight: 'bold' } } }, scales: { x: { grid: { color: 'rgba(255, 255, 255, 0.05)' }, ticks: { color: '#64748b', font: { family: 'JetBrains Mono', size: 10 } } }, y: { grid: { color: 'rgba(255, 255, 255, 0.05)' }, ticks: { color: '#64748b', font: { family: 'JetBrains Mono', size: 10 } } } } }; // 1. Reward & Success Rate Chart const ctxReward = document.getElementById('rewardChart').getContext('2d'); this.rewardChart = new Chart(ctxReward, { type: 'line', data: { labels: this.historyData.timesteps.map(s => `${s / 1000}k`), datasets: [ { label: 'Episode Reward', data: this.historyData.rawRewards, borderColor: 'rgba(79, 172, 254, 0.35)', backgroundColor: 'transparent', borderWidth: 1, pointRadius: 0, tension: 0.1 }, { label: '100-MA Mean Reward', data: this.historyData.maRewards, borderColor: '#00f2fe', backgroundColor: 'rgba(0, 242, 254, 0.1)', fill: true, borderWidth: 2.5, pointRadius: 0, tension: 0.3 } ] }, options: { ...commonChartOptions, onClick: (event, elements) => { if (elements.length > 0) { const index = elements[0].index; const step = this.historyData.timesteps[index]; if (this.onTimelineClick) this.onTimelineClick(step); } } } }); // 2. Policy & Value Loss Chart const ctxLoss = document.getElementById('lossChart').getContext('2d'); this.lossChart = new Chart(ctxLoss, { type: 'line', data: { labels: this.historyData.timesteps.map(s => `${s / 1000}k`), datasets: [ { label: 'Value Loss (VF)', data: this.historyData.valueLosses, borderColor: '#9d4edd', backgroundColor: 'rgba(157, 78, 221, 0.1)', fill: true, borderWidth: 2, pointRadius: 0, yAxisID: 'y' }, { label: 'Policy Loss (x100)', data: this.historyData.policyLosses.map(v => v * 100), borderColor: '#f72585', backgroundColor: 'transparent', borderWidth: 1.5, pointRadius: 0, yAxisID: 'y1' } ] }, options: { ...commonChartOptions, scales: { ...commonChartOptions.scales, y1: { position: 'right', grid: { drawOnChartArea: false }, ticks: { color: '#f72585', font: { family: 'JetBrains Mono', size: 10 } } } } } }); } // Update dynamic metric values when training simulation runs addLiveStep(step, reward, pLoss, vLoss, successRate) { const label = `${(step / 1000).toFixed(1)}k`; this.rewardChart.data.labels.push(label); this.rewardChart.data.datasets[0].data.push(reward); const lastMA = this.rewardChart.data.datasets[1].data.slice(-1)[0] || reward; const newMA = lastMA * 0.9 + reward * 0.1; this.rewardChart.data.datasets[1].data.push(newMA); this.lossChart.data.labels.push(label); this.lossChart.data.datasets[0].data.push(vLoss); this.lossChart.data.datasets[1].data.push(pLoss * 100); if (this.rewardChart.data.labels.length > 80) { this.rewardChart.data.labels.shift(); this.rewardChart.data.datasets[0].data.shift(); this.rewardChart.data.datasets[1].data.shift(); this.lossChart.data.labels.shift(); this.lossChart.data.datasets[0].data.shift(); this.lossChart.data.datasets[1].data.shift(); } this.rewardChart.update('none'); this.lossChart.update('none'); } } window.RLMetricsDashboard = RLMetricsDashboard;