fix: only show waveforms when live monitor data is flowing
This commit is contained in:
@@ -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;
|
||||
|
||||
// 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;
|
||||
|
||||
if (hasEcgData || hasSpo2Data || hasRespData) {
|
||||
// Advance scan position
|
||||
const prevScanIndex = scanIndex;
|
||||
scanIndex = (scanIndex + speed) % WAVE_BUFFER_SIZE;
|
||||
|
||||
// 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
|
||||
const hr = currentHR || 75;
|
||||
const rr = currentRR || 16;
|
||||
|
||||
// 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 ecgPhaseInc = (hr / 60) * (speed / WAVE_BUFFER_SIZE) * 4.0;
|
||||
const respPhaseInc = (rr / 60) * (speed / WAVE_BUFFER_SIZE) * 4.0;
|
||||
|
||||
// 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;
|
||||
|
||||
ecgPhase += ecgPhaseInc / speed;
|
||||
plethPhase += ecgPhaseInc / speed; // Sync SpO2 with ECG
|
||||
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); // Offset phase slightly for Lead I
|
||||
waveBuffers.pleth[targetIdx] = getPlethValue(plethPhase - 0.2); // Pulse wave lag
|
||||
waveBuffers.resp[targetIdx] = getRespValue(respPhase);
|
||||
waveBuffers.ecg1[targetIdx] = getECG1Value(ecgPhase + 0.15);
|
||||
} else {
|
||||
waveBuffers.ecg2[targetIdx] = 0;
|
||||
waveBuffers.ecg1[targetIdx] = 0;
|
||||
}
|
||||
|
||||
// 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
|
||||
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'));
|
||||
}
|
||||
|
||||
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';
|
||||
|
||||
Reference in New Issue
Block a user