fix: only show waveforms when live monitor data is flowing

This commit is contained in:
2026-07-15 13:00:19 +05:30
parent 61f977e718
commit 78500c487a
+78 -29
View File
@@ -1270,6 +1270,17 @@
currentHR = null;
currentSpO2 = null;
currentRR = null;
hasReceivedData = false;
// Reset wave buffers so sweep starts fresh on next data
scanIndex = 0;
ecgPhase = 0;
plethPhase = 0;
respPhase = 0;
waveBuffers.ecg2.fill(0);
waveBuffers.ecg1.fill(0);
waveBuffers.pleth.fill(0);
waveBuffers.resp.fill(0);
document.getElementById('value-hr').textContent = '---';
document.getElementById('value-hr').classList.add('no-data');
@@ -1289,47 +1300,85 @@
// === Animation Loop (Real-time Scanning Sweep Bar) ===
function animateWaveforms() {
// Speed of the sweep bar (in virtual coordinates per frame)
const speed = 4;
// Advance scan position
const prevScanIndex = scanIndex;
scanIndex = (scanIndex + speed) % WAVE_BUFFER_SIZE;
// Only generate and draw sweep waveforms when live data is flowing
const hasEcgData = hasReceivedData && currentHR !== null;
const hasSpo2Data = hasReceivedData && currentSpO2 !== null;
const hasRespData = hasReceivedData && currentRR !== null;
// Increment procedural wave phases
// Scale phase speed depending on live vitals rate (beats/breaths per minute)
const hr = currentHR || 75; // fallback to 75 bpm
const rr = currentRR || 16; // fallback to 16 breaths
if (hasEcgData || hasSpo2Data || hasRespData) {
// Advance scan position
const prevScanIndex = scanIndex;
scanIndex = (scanIndex + speed) % WAVE_BUFFER_SIZE;
// Increment phase
// For ECG/Pleth, 1 cycle is 1 heartbeat.
// For Resp, 1 cycle is 1 breath.
const ecgPhaseInc = (hr / 60) * (speed / WAVE_BUFFER_SIZE) * 4.0; // 4 seconds sweep
const respPhaseInc = (rr / 60) * (speed / WAVE_BUFFER_SIZE) * 4.0;
const hr = currentHR || 75;
const rr = currentRR || 16;
// Compute values and populate circular buffers for the newly scanned segment
for (let i = 0; i < speed; i++) {
const targetIdx = (prevScanIndex + i) % WAVE_BUFFER_SIZE;
const ecgPhaseInc = (hr / 60) * (speed / WAVE_BUFFER_SIZE) * 4.0;
const respPhaseInc = (rr / 60) * (speed / WAVE_BUFFER_SIZE) * 4.0;
ecgPhase += ecgPhaseInc / speed;
plethPhase += ecgPhaseInc / speed; // Sync SpO2 with ECG
respPhase += respPhaseInc / speed;
for (let i = 0; i < speed; i++) {
const targetIdx = (prevScanIndex + i) % WAVE_BUFFER_SIZE;
waveBuffers.ecg2[targetIdx] = getECGValue(ecgPhase);
waveBuffers.ecg1[targetIdx] = getECG1Value(ecgPhase + 0.15); // Offset phase slightly for Lead I
waveBuffers.pleth[targetIdx] = getPlethValue(plethPhase - 0.2); // Pulse wave lag
waveBuffers.resp[targetIdx] = getRespValue(respPhase);
ecgPhase += ecgPhaseInc / speed;
plethPhase += ecgPhaseInc / speed;
respPhase += respPhaseInc / speed;
// Only fill buffers for channels with live data
if (hasEcgData) {
waveBuffers.ecg2[targetIdx] = getECGValue(ecgPhase);
waveBuffers.ecg1[targetIdx] = getECG1Value(ecgPhase + 0.15);
} else {
waveBuffers.ecg2[targetIdx] = 0;
waveBuffers.ecg1[targetIdx] = 0;
}
if (hasSpo2Data) {
waveBuffers.pleth[targetIdx] = getPlethValue(plethPhase - 0.2);
} else {
waveBuffers.pleth[targetIdx] = 0;
}
if (hasRespData) {
waveBuffers.resp[targetIdx] = getRespValue(respPhase);
} else {
waveBuffers.resp[targetIdx] = 0;
}
}
// Draw active channels with sweep, idle channels with flat baseline
drawChannel('ecg2', waveBuffers.ecg2, varColor('--ecg-color'), 1.8, false, hasEcgData);
drawChannel('ecg1', waveBuffers.ecg1, varColor('--ecg-color'), 1.8, false, hasEcgData);
drawChannel('pleth', waveBuffers.pleth, varColor('--spo2-color'), 2.2, false, hasSpo2Data);
drawChannel('resp', waveBuffers.resp, varColor('--resp-color'), 1.8, true, hasRespData);
} else {
// No live data at all — draw dim flat baselines on every channel
drawFlatBaseline('ecg2', varColor('--ecg-color'));
drawFlatBaseline('ecg1', varColor('--ecg-color'));
drawFlatBaseline('pleth', varColor('--spo2-color'));
drawFlatBaseline('resp', varColor('--resp-color'));
}
// Draw all wave channels
drawChannel('ecg2', waveBuffers.ecg2, varColor('--ecg-color'), 1.8, false);
drawChannel('ecg1', waveBuffers.ecg1, varColor('--ecg-color'), 1.8, false);
drawChannel('pleth', waveBuffers.pleth, varColor('--spo2-color'), 2.2, false);
drawChannel('resp', waveBuffers.resp, varColor('--resp-color'), 1.8, true); // Respiration is dotted/dashed
requestAnimationFrame(animateWaveforms);
}
function drawFlatBaseline(key, color) {
const wf = canvases[key];
if (!wf) return;
const { ctx, width, height } = wf;
ctx.clearRect(0, 0, width, height);
ctx.beginPath();
ctx.strokeStyle = color;
ctx.globalAlpha = 0.15;
ctx.lineWidth = 1;
ctx.setLineDash([]);
ctx.moveTo(0, height / 2);
ctx.lineTo(width, height / 2);
ctx.stroke();
ctx.globalAlpha = 1.0;
}
function varColor(varName) {
// Fallback colors if styling var fails
if (varName.includes('ecg-color')) return '#00ff66';