Files
radiacode/web/static/js/background.js
Jacquin Antoine edaceae8a3 Fix: reset zoom restores full energy range (0.33-3035.67 keV)
Use {x,y} data format so Chart.js linear scale uses energy values
instead of channel indices. After reset, force scale min/max to the
full energy range.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 01:25:33 +02:00

269 lines
9.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

let bgChart = null;
let bgReferenceData = null;
let bgContinuumData = null;
// Reset zoom button — fully restores the energy range
document.getElementById('reset-zoom-bg')?.addEventListener('click', () => {
if (bgChart) {
bgChart.resetZoom();
// After resetZoom, force the scale to full energy range
const firstPt = bgChart.data.datasets[0]?.data?.[0];
const lastPt = bgChart.data.datasets[0]?.data?.[bgChart.data.datasets[0].data.length - 1];
const fullMin = firstPt?.x ?? 0;
const fullMax = lastPt?.x ?? 3036;
bgChart.options.scales.x.min = fullMin;
bgChart.options.scales.x.max = fullMax;
bgChart.update();
document.getElementById('reset-zoom-bg').style.display = 'none';
}
});
async function loadBgReference() {
try {
const resp = await fetch(`${API_BASE}/api/background/reference`);
if (!resp.ok) return;
bgReferenceData = await resp.json();
} catch {}
}
async function loadBgContinuum(cps, liveTime) {
try {
const resp = await fetch(`${API_BASE}/api/background/continuum?cps=${cps}&live_time_s=${liveTime}`);
if (!resp.ok) return;
bgContinuumData = await resp.json();
} catch {}
}
/**
* Gaussian kernel smoothing.
* Convolves the data with a Gaussian kernel of given sigma (in channels).
* Preserves peak shapes while reducing statistical variation.
*/
function smoothGaussian(data, sigma) {
if (!data || data.length === 0) return data;
const kernelRadius = Math.ceil(sigma * 3);
const kernel = [];
for (let i = -kernelRadius; i <= kernelRadius; i++) {
kernel.push(Math.exp(-0.5 * (i / sigma) ** 2));
}
const result = new Array(data.length);
for (let i = 0; i < data.length; i++) {
let sum = 0;
let wSum = 0;
for (let k = -kernelRadius; k <= kernelRadius; k++) {
const idx = i + k;
if (idx < 0 || idx >= data.length) continue;
const w = kernel[k + kernelRadius];
sum += data[idx] * w;
wSum += w;
}
result[i] = wSum > 0 ? sum / wSum : 0;
}
return result;
}
async function refreshBackground() {
try {
const [infoResp, specResp] = await Promise.all([
fetch(`${API_BASE}/api/background`),
fetch(`${API_BASE}/api/background/spectrum`)
]);
if (!infoResp.ok || !specResp.ok) {
document.getElementById('bg-stats').innerHTML = '<p style="color:#888">Background non disponible</p>';
return;
}
const info = await infoResp.json();
const spec = await specResp.json();
// Stats
document.getElementById('bg-stats').innerHTML = `
<div class="bg-stat"><div class="bg-stat-value">${info.elapsed_hours.toFixed(1)}h</div><div class="bg-stat-label">Durée</div></div>
<div class="bg-stat"><div class="bg-stat-value">${info.live_time_s.toFixed(0)}s</div><div class="bg-stat-label">Live time</div></div>
<div class="bg-stat"><div class="bg-stat-value">${info.total_counts.toFixed(0)}</div><div class="bg-stat-label">Coups</div></div>
<div class="bg-stat"><div class="bg-stat-value">${info.cps.toFixed(2)}</div><div class="bg-stat-label">CPS</div></div>
`;
// Load continuum on first load
if (!bgContinuumData && spec.live_time_s > 0) {
await loadBgContinuum(info.cps || 6.0, spec.live_time_s);
}
// Chart
updateBackgroundChart(spec);
// Peaks table
updatePeaksTable(info.top_peaks || []);
// Show/hide toggles
const refToggle = document.getElementById('show-bg-reference');
if (refToggle) refToggle.parentElement.style.display = spec.reference_available ? 'flex' : 'none';
} catch {}
}
function updateBackgroundChart(spec) {
const showLog = document.getElementById('bg-scale-log')?.checked;
const ctx = document.getElementById('background-chart').getContext('2d');
const showRef = document.getElementById('show-bg-reference')?.checked && bgReferenceData;
const showSmooth = document.getElementById('show-bg-smooth')?.checked;
const showContinuum = document.getElementById('show-bg-continuum')?.checked && bgContinuumData;
const energy = spec.energy_kev;
const toData = (arr) => arr.map((v, i) => ({ x: energy[i], y: v }));
const datasets = [{
label: 'Background (live)',
data: toData(spec.counts),
borderColor: '#ff9800',
backgroundColor: 'rgba(255, 152, 0, 0.1)',
borderWidth: 1,
pointRadius: 0,
fill: true,
}];
if (showSmooth) {
const smoothed = smoothGaussian(spec.counts, 8);
datasets.push({
label: 'Lissé',
data: toData(smoothed),
borderColor: 'rgba(233, 30, 99, 0.9)',
backgroundColor: 'rgba(233, 30, 99, 0.05)',
borderWidth: 2,
pointRadius: 0,
fill: false,
});
}
if (showContinuum) {
datasets.push({
label: 'Continuum',
data: toData(bgContinuumData.counts),
borderColor: 'rgba(156, 39, 176, 0.8)',
backgroundColor: 'rgba(156, 39, 176, 0.05)',
borderWidth: 2,
pointRadius: 0,
fill: false,
borderDash: [8, 4],
});
}
if (showRef) {
const scale = spec.live_time_s > 0 && bgReferenceData.live_time_s > 0
? spec.live_time_s / bgReferenceData.live_time_s
: 1;
const refEnergy = bgReferenceData.energy_kev || energy;
datasets.push({
label: `Référence 24h (×${scale.toFixed(1)})`,
data: bgReferenceData.counts.map((c, i) => ({ x: refEnergy[i] ?? energy[i], y: c * scale })),
borderColor: 'rgba(79, 195, 247, 0.8)',
backgroundColor: 'rgba(79, 195, 247, 0.08)',
borderWidth: 1,
pointRadius: 0,
fill: true,
borderDash: [4, 2],
});
}
const chartData = {
datasets: datasets,
};
// Preserve pan range (user zoomed), but reset to full range when data refreshes
const panRange = bgChart?._panRange;
const firstX = datasets[0].data[0]?.x;
const lastX = datasets[0].data[datasets[0].data.length - 1]?.x;
const xMin = panRange ? panRange[0] : (firstX ?? 0);
const xMax = panRange ? panRange[1] : (lastX ?? 3036);
const options = {
responsive: true,
maintainAspectRatio: false,
interaction: { mode: 'index', intersect: false },
plugins: {
legend: { labels: { color: '#e0e0e0' } },
tooltip: {
enabled: true,
mode: 'index',
intersect: false,
callbacks: {
title: (items) => `${items[0].parsed.x.toFixed(1)} keV`,
label: (item) => `${item.dataset.label}: ${item.parsed.y.toFixed(1)} counts`
}
},
zoom: {
zoom: {
wheel: { enabled: true },
pinch: { enabled: true },
drag: { enabled: false },
mode: 'x',
onZoomComplete: () => {
document.getElementById('reset-zoom-bg').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: showLog ? 'logarithmic' : 'linear',
title: { display: true, text: `Comptages (${showLog ? 'log' : 'lin'})`, color: '#888' },
...(showLog ? { min: 0.9 } : {}),
ticks: { color: '#888' },
grid: { color: '#333' },
}
}
};
if (bgChart) {
bgChart.data = chartData;
bgChart.options = options;
bgChart.update();
} else {
bgChart = new Chart(ctx, { type: 'line', data: chartData, ...options });
const firstX = datasets[0].data[0]?.x ?? 0;
const lastX = datasets[0].data[datasets[0].data.length - 1]?.x ?? 3036;
enablePan(bgChart, 'reset-zoom-bg', firstX, lastX);
}
}
function updatePeaksTable(peaks) {
const container = document.getElementById('peaks-table');
if (!peaks || peaks.length === 0) {
container.innerHTML = '<p style="color:#888;text-align:center;padding:8px;">Pas assez de données pour identifier les pics</p>';
return;
}
let html = '<h3 style="margin-bottom:8px;color:#ff9800;">Pics détectés</h3>';
peaks.forEach(p => {
html += `<div class="peak-row">
<span style="color:#4fc3f7">${p.energy_kev.toFixed(1)} keV</span>
<span>${p.counts.toFixed(0)} cts</span>
</div>`;
});
container.innerHTML = html;
}
document.querySelector('[data-tab="background"]').addEventListener('click', () => {
refreshBackground();
loadBgReference();
});
// Toggle handlers
document.getElementById('show-bg-reference')?.addEventListener('change', () => refreshBackground());
document.getElementById('show-bg-continuum')?.addEventListener('change', () => {
if (document.getElementById('show-bg-continuum').checked && !bgContinuumData) {
loadBgContinuum(6.0, 3600).then(() => refreshBackground());
} else {
refreshBackground();
}
});
document.getElementById('show-bg-smooth')?.addEventListener('change', () => refreshBackground());
document.getElementById('bg-scale-log')?.addEventListener('change', () => refreshBackground());