let cpsChart = null; async function loadCps(hours = 24) { // Highlight active button document.querySelectorAll('.controls button').forEach(b => b.style.opacity = '0.6'); const activeBtn = [...document.querySelectorAll('.controls button')].find( b => b.textContent.includes(hours <= 24 ? `${hours}h` : `${hours/24}j`) || (hours === 168 && b.textContent === '7j') ); if (activeBtn) activeBtn.style.opacity = '1'; try { const resp = await fetch(`${API_BASE}/api/cps/timeline?hours=${hours}`); if (!resp.ok) return; const data = await resp.json(); if (!data.data_points || data.data_points.length === 0) { return; } const labels = data.data_points.map(d => d.ts); const cpsValues = data.data_points.map(d => d.cps); updateCpsChart(labels, cpsValues); } catch {} } function updateCpsChart(labels, values) { const ctx = document.getElementById('cps-chart').getContext('2d'); const chartData = { labels: labels, datasets: [{ label: 'CPS', data: values, borderColor: '#4caf50', backgroundColor: 'rgba(76, 175, 80, 0.1)', borderWidth: 1.5, pointRadius: 0, fill: true, tension: 0.3, }] }; const panRange = cpsChart?._panRange; const xMin = panRange ? panRange[0] : labels[0]; const xMax = panRange ? panRange[1] : labels[labels.length - 1]; const options = { responsive: true, maintainAspectRatio: false, interaction: { mode: 'index', intersect: false }, plugins: { legend: { labels: { color: '#e0e0e0' } }, tooltip: { enabled: true, mode: 'index', intersect: false, filter: (item) => item.raw != null, callbacks: { title: (items) => { const d = new Date(items[0].parsed.x / 1000); return d.toLocaleString('fr-FR', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }); }, label: (item) => `${item.dataset.label}: ${item.raw.toFixed(2)}` } }, zoom: { zoom: { wheel: { enabled: true }, pinch: { enabled: true }, drag: { enabled: false }, mode: 'x', onZoomComplete: () => { document.getElementById('reset-zoom-cps').style.display = 'inline-block'; } } } }, scales: { x: { type: 'time', min: xMin, max: xMax, time: { tooltipFormat: 'dd/MM HH:mm', displayFormats: { minute: 'HH:mm', hour: 'HH:mm', day: 'dd/MM' } }, title: { display: true, text: 'Temps', color: '#888' }, ticks: { color: '#888', maxTicksLimit: 12 }, grid: { color: '#333' }, }, y: { title: { display: true, text: 'CPS', color: '#888' }, ticks: { color: '#888' }, grid: { color: '#333' }, beginAtZero: true, } } }; if (cpsChart) { cpsChart.data = chartData; cpsChart.options = options; cpsChart.update(); } else { // Chart.js needs the date adapter for time axis const script = document.createElement('script'); script.src = 'https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0/dist/chartjs-adapter-date-fns.bundle.min.js'; script.onload = () => { cpsChart = new Chart(ctx, { type: 'line', data: chartData, ...options }); enablePan(cpsChart, 'reset-zoom-cps', labels[0], labels[labels.length - 1]); }; document.head.appendChild(script); } } // Reset zoom — restore full time range document.getElementById('reset-zoom-cps')?.addEventListener('click', () => { if (cpsChart) { cpsChart.resetZoom(); delete cpsChart._panRange; const labels = cpsChart.data.labels; cpsChart.options.scales.x.min = labels[0]; cpsChart.options.scales.x.max = labels[labels.length - 1]; cpsChart.update(); document.getElementById('reset-zoom-cps').style.display = 'none'; } });