feat(viewer): v4 time-series graphs (depth, PWM USV/AUV, status)

This commit is contained in:
Poulpe
2026-04-25 22:15:43 +00:00
parent 3198164aff
commit 103bf1cedd
4 changed files with 372 additions and 4 deletions

View File

@@ -2,13 +2,13 @@
<html lang="fr">
<head>
<meta charset="utf-8">
<title>COSMA — NAV Viewer v3</title>
<title>COSMA — NAV Viewer v4</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/noUiSlider/15.7.1/nouislider.min.css"/>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: monospace; background: #1a1a2e; color: #e0e0e0; display: flex; flex-direction: column; height: 100vh; }
body { font-family: monospace; background: #1a1a2e; color: #e0e0e0; display: flex; flex-direction: column; min-height: 100vh; }
#map { flex: 1; min-height: 0; }
#controls {
background: #16213e;
@@ -89,7 +89,13 @@
#btn-auv { color: #ff8800; border-color: #ff8800; }
#btn-vec { color: #888; border-color: #888; }
#btn-usbl-panel { color: #aaa; border-color: #444; }
#graphs-section { background:#12122a; border-top:1px solid #0f3460; overflow-y:auto; max-height:40vh; flex-shrink:0; }
.chart-container { padding:6px 12px 4px; border-bottom:1px solid #0f3460; }
.chart-container canvas { display:block; }
.chart-title { font-size:10px; color:#a0c4ff; margin-bottom:2px; font-family:monospace; }
</style>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-annotation@3/dist/chartjs-plugin-annotation.min.js"></script>
</head>
<body>
<div id="map"></div>
@@ -198,6 +204,77 @@ let trackLayers = [];
let auvTrackLayer = null;
let windowPoints = [];
let usblWindow = [];
// == Graph state ==
let charts = {};
let mcapSignals = null;
let usvPwm = null;
const PWM_COLORS_AUV = ['#ff8800','#ffd166','#06d6a0','#00b4d8','#a855f7','#f97316','#e94560','#88c0d0'];
const PWM_COLORS_USV = ['#00b4d8','#0096b4','#0077a0','#005f8c','#ffd166','#e6b800','#c49a00','#a07d00'];
function makeChartOptions() {
return {
animation: false, parsing: false, responsive: true, maintainAspectRatio: false,
plugins: {
legend: { display: false, labels: { color:'#a0c4ff', font:{size:9,family:'monospace'}, boxWidth:12 } },
annotation: { annotations: {} }
},
scales: {
x: { type:'linear', ticks:{ color:'#666', font:{size:9,family:'monospace'}, maxTicksLimit:8,
callback:(v)=>new Date(v).toISOString().substr(11,8) }, grid:{color:'#1a1a3e'} },
y: { ticks:{ color:'#666', font:{size:9,family:'monospace'}, maxTicksLimit:5 }, grid:{color:'#1a1a3e'} },
},
};
}
function initCharts() {
const mkDs = (lbl, col) => ({ label:lbl, data:[], borderColor:col, borderWidth:1.5, pointRadius:0, tension:0 });
charts.depth = new Chart(document.getElementById('chart-depth'), { type:'line', data:{datasets:[mkDs('depth','#06d6a0')]}, options:makeChartOptions() });
charts.pwmAuv = new Chart(document.getElementById('chart-pwm-auv'), { type:'line', data:{datasets:[]}, options:makeChartOptions() });
charts.pwmUsv = new Chart(document.getElementById('chart-pwm-usv'), { type:'line', data:{datasets:[]}, options:makeChartOptions() });
charts.usbl = new Chart(document.getElementById('chart-usbl'), { type:'line', data:{datasets:[mkDs('dist','#a855f7')]}, options:makeChartOptions() });
}
function updateGraphCursor(t_ms) {
const ann = { type:'line', xMin:t_ms, xMax:t_ms, borderColor:'#e94560', borderWidth:1.5, borderDash:[4,2] };
for (const c of Object.values(charts)) { c.options.plugins.annotation.annotations = {cursor:ann}; c.update('none'); }
}
function updateGraphWindow(t0, t1) {
for (const c of Object.values(charts)) { c.options.scales.x.min=t0; c.options.scales.x.max=t1; c.update('none'); }
}
function populateCharts() {
if (mcapSignals) {
if (mcapSignals.depth) {
charts.depth.data.datasets[0].data = mcapSignals.depth.map(p=>({x:p.t,y:p.v}));
charts.depth.update('none');
}
if (mcapSignals.pwm_auv) {
const {channels,samples} = mcapSignals.pwm_auv;
charts.pwmAuv.data.datasets = channels.map((ch,i)=>({
label:'Ch'+ch, data:samples.map(s=>({x:s.t,y:s.v[i]})),
borderColor:PWM_COLORS_AUV[i%8], borderWidth:1, pointRadius:0, tension:0
}));
charts.pwmAuv.options.plugins.legend.display = channels.length > 1;
charts.pwmAuv.update('none');
}
}
if (usvPwm && usvPwm.M) {
const keys = Object.keys(usvPwm.M).sort();
charts.pwmUsv.data.datasets = keys.map((k,i)=>({
label:k, data:usvPwm.M[k].map(p=>({x:p.t,y:p.v})),
borderColor:PWM_COLORS_USV[i%8], borderWidth:1, pointRadius:0, tension:0
}));
charts.pwmUsv.options.plugins.legend.display = keys.length > 1;
charts.pwmUsv.update('none');
}
if (usblPoints && usblPoints.length) {
charts.usbl.data.datasets[0].data = usblPoints.map(p=>({x:p.t_ms, y:p.dist!==undefined?p.dist:p.distance}));
charts.usbl.update('none');
}
}
let cursorMarker = null;
let auvMarker = null;
let usblVector = null;
@@ -469,7 +546,7 @@ async function loadData() {
if (tMin === tMax) tMax = tMin + 1000;
document.getElementById('title').textContent =
`COSMA v3 — USV ${manifest.n_sessions} sess. ${manifest.n_points_sampled} pts | AUV ${usblMeta.n_points} pts`;
`COSMA v4 — USV ${manifest.n_sessions} sess. ${manifest.n_points_sampled} pts | AUV ${usblMeta.n_points} pts`;
buildLegend();
initSliders();
@@ -483,7 +560,31 @@ async function loadData() {
}
}
loadData();
async function loadGraphData() {
initCharts();
try {
const [mcapResp, usvPwmResp] = await Promise.allSettled([
fetch('data/mcap_signals.json'),
fetch('data/usv_pwm.json'),
]);
if (mcapResp.status === 'fulfilled' && mcapResp.value.ok) {
mcapSignals = await mcapResp.value.json();
}
if (usvPwmResp.status === 'fulfilled' && usvPwmResp.value.ok) {
usvPwm = await usvPwmResp.value.json();
}
populateCharts();
} catch (e) { console.warn('Graph data load error:', e); }
}
loadData().then(() => { populateCharts(); });
loadGraphData();
</script>
<div id="graphs-section">
<div class="chart-container"><div class="chart-title">Profondeur AUV (m)</div><canvas id="chart-depth" height="80"></canvas></div>
<div class="chart-container"><div class="chart-title">PWM AUV (canaux)</div><canvas id="chart-pwm-auv" height="80"></canvas></div>
<div class="chart-container"><div class="chart-title">PWM USV (M1-M8)</div><canvas id="chart-pwm-usv" height="80"></canvas></div>
<div class="chart-container"><div class="chart-title">USBL Distance (m)</div><canvas id="chart-usbl" height="60"></canvas></div>
</div>
</body>
</html>