let spectrumChart = null; let currentSpectrumData = null; async function refreshSpectrum() { const showDiff = document.getElementById('show-difference').checked; const endpoint = showDiff ? '/api/spectrum/difference' : '/api/spectrum/current'; try { const resp = await fetch(`${API_BASE}${endpoint}`); if (!resp.ok) return; const data = await resp.json(); currentSpectrumData = data; updateSpectrumChart(data); updateIsotopesTable(data.isotopes_detected || []); } catch {} } function updateSpectrumChart(data) { const logScale = document.getElementById('log-scale').checked; const showLines = document.getElementById('show-isotope-lines').checked; const detectedOnly = document.getElementById('lines-detected-only').checked; const showBgOverlay = document.getElementById('show-bg-overlay').checked; const ctx = document.getElementById('spectrum-chart').getContext('2d'); const toData = (counts, energies) => counts.map((v, i) => ({ x: energies[i], y: v })); const energy = data.energy_kev; const datasets = [{ label: data.background_subtracted ? 'Spectre (background soustrait)' : 'Spectre cumulé', data: toData(data.counts, energy), borderColor: '#4fc3f7', backgroundColor: 'rgba(79, 195, 247, 0.1)', borderWidth: 1, pointRadius: 0, fill: logScale ? 'origin' : true, tension: 0.1, }]; // Overlay background if requested and available, scaled to match spectrum max if (showBgOverlay && bgOverlayData) { const specMax = Math.max(...data.counts); const bgMax = Math.max(...bgOverlayData.counts); const bgScale = bgMax > 0 ? specMax / bgMax : 1; const bgEnergy = bgOverlayData.energy_kev || energy; datasets.push({ label: 'Background', data: bgOverlayData.counts.map((v, i) => ({ x: bgEnergy[i] ?? energy[i], y: v * bgScale })), borderColor: 'rgba(255, 152, 0, 0.6)', backgroundColor: 'rgba(255, 152, 0, 0.05)', borderWidth: 1, pointRadius: 0, fill: true, tension: 0.1, }); } const chartData = { datasets: datasets, }; // Annotations let annotations = {}; if (showLines) { annotations = buildIsotopeAnnotations(detectedOnly, (data.isotopes_detected || []).map(i => i.isotope)); } const firstPt = datasets[0].data[0]; const lastPt = datasets[0].data[datasets[0].data.length - 1]; const panRange = spectrumChart?._panRange; const xMin = panRange ? panRange[0] : (firstPt?.x ?? 0); const xMax = panRange ? panRange[1] : (lastPt?.x ?? 3000); const options = { responsive: true, maintainAspectRatio: false, animation: { duration: 300 }, interaction: { mode: 'index', intersect: false }, plugins: { legend: { labels: { color: '#e0e0e0' } }, tooltip: { enabled: true, mode: 'index', intersect: false, filter: (item) => item.parsed.y != null, callbacks: { title: (items) => `${items[0].parsed.x.toFixed(1)} keV`, label: (item) => `${item.dataset.label}: ${item.parsed.y.toFixed(1)} counts` } }, annotation: { annotations: annotations }, zoom: { zoom: { wheel: { enabled: true }, pinch: { enabled: true }, drag: { enabled: false }, mode: 'x', onZoomComplete: () => { document.getElementById('reset-zoom-spectrum').style.display = 'inline-block'; } } } }, scales: { x: { type: 'linear', min: xMin, max: xMax, title: { display: true, text: 'Énergie (keV)', color: '#888' }, ticks: { color: '#888', maxTicksLimit: 20 }, grid: { color: '#333' }, }, y: { type: logScale ? 'logarithmic' : 'linear', min: logScale ? 0.5 : undefined, title: { display: true, text: logScale ? 'Comptages (log)' : 'Comptages', color: '#888' }, ticks: { color: '#888' }, grid: { color: '#333' }, } } }; if (spectrumChart) { spectrumChart.data = chartData; spectrumChart.options = options; spectrumChart.update(); } else { spectrumChart = new Chart(ctx, { type: 'line', data: chartData, ...options }); const panMin = firstPt?.x ?? 0; const panMax = lastPt?.x ?? 3000; enablePan(spectrumChart, 'reset-zoom-spectrum', panMin, panMax); // Fix: Chart.js may read wrong canvas dimensions on first render; // resize on next frame ensures layout is fully computed. requestAnimationFrame(() => spectrumChart.resize()); } } function updateIsotopesTable(isotopes) { const container = document.getElementById('isotopes-table'); if (!isotopes || isotopes.length === 0) { container.innerHTML = '
Aucun isotope détecté (background uniquement)
'; return; } let html = '