// ── Chronograph Analyser page logic ─────────────────────────────────────────── let sessions = []; let currentSessionId = null; let _photoTargetGroupId = null; // group id for current photo upload modal // showToast, renderPublicToggle → utils.js // ── Sessions list ───────────────────────────────────────────────────────────── async function loadSessions() { try { sessions = await apiGetAll('/tools/chronograph/'); renderSessionsList(); } catch(e) { document.getElementById('sessionsSpinner').innerHTML = '
Failed to load sessions.
'; } } function renderSessionsList() { document.getElementById('sessionsSpinner').classList.add('d-none'); const list = document.getElementById('sessionsList'); const empty = document.getElementById('sessionsEmpty'); if (!sessions.length) { empty.classList.remove('d-none'); list.innerHTML = ''; return; } empty.classList.add('d-none'); list.innerHTML = sessions.map(s => `Failed to load analysis.
'; } } function fmtFps(v) { return v != null ? `${Number(v).toFixed(1)} fps` : '—'; } function renderAnalysis(detail, charts) { document.getElementById('chartsSpinner').classList.add('d-none'); document.getElementById('chartsContent').classList.remove('d-none'); // Overall stats from all shots const allShots = (detail.shot_groups || []).flatMap(g => g.shots || []); const speeds = allShots.map(s => parseFloat(s.velocity_fps)).filter(v => !isNaN(v)); const overallStats = computeStats(speeds); document.getElementById('overallStatsWrap').innerHTML = renderStatsTable(overallStats); // Overview chart const overviewImg = document.getElementById('overviewChart'); if (charts.overview) { overviewImg.src = `data:image/png;base64,${charts.overview}`; overviewImg.style.display = ''; } else { overviewImg.style.display = 'none'; } // Per-group sections const groups = detail.shot_groups || []; const groupCharts = charts.groups || []; const groupHtml = groups.map((g, i) => { const gSpeeds = (g.shots || []).map(s => parseFloat(s.velocity_fps)).filter(v => !isNaN(v)); const gStats = computeStats(gSpeeds); const chartB64 = groupCharts[i]; return `No chart available.
'}No shot groups in this session.
'; // Load photos for each group asynchronously groups.forEach(g => loadGroupPhotos(g.id)); } function computeStats(speeds) { if (!speeds.length) return null; const n = speeds.length; const avg = speeds.reduce((a,b) => a+b, 0) / n; const min = Math.min(...speeds); const max = Math.max(...speeds); const es = max - min; const sd = n > 1 ? Math.sqrt(speeds.reduce((s,v) => s + (v-avg)**2, 0) / (n-1)) : null; return { n, avg, min, max, es, sd }; } function renderStatsTable(stats) { if (!stats) return 'No data.
'; const sdText = stats.sd != null ? `${stats.sd.toFixed(1)} fps` : '—'; return `| Shots | ${stats.n} |
| Average | ${stats.avg.toFixed(1)} fps |
| Min | ${stats.min.toFixed(1)} fps |
| Max | ${stats.max.toFixed(1)} fps |
| Extreme spread (ES) | ${stats.es.toFixed(1)} fps |
| Std deviation (SD) | ${sdText} |