fix: only show waveforms when live monitor data is flowing
This commit is contained in:
@@ -1270,6 +1270,17 @@
|
|||||||
currentHR = null;
|
currentHR = null;
|
||||||
currentSpO2 = null;
|
currentSpO2 = null;
|
||||||
currentRR = 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').textContent = '---';
|
||||||
document.getElementById('value-hr').classList.add('no-data');
|
document.getElementById('value-hr').classList.add('no-data');
|
||||||
@@ -1289,47 +1300,85 @@
|
|||||||
// === Animation Loop (Real-time Scanning Sweep Bar) ===
|
// === Animation Loop (Real-time Scanning Sweep Bar) ===
|
||||||
|
|
||||||
function animateWaveforms() {
|
function animateWaveforms() {
|
||||||
// Speed of the sweep bar (in virtual coordinates per frame)
|
|
||||||
const speed = 4;
|
const speed = 4;
|
||||||
|
|
||||||
// Advance scan position
|
// Only generate and draw sweep waveforms when live data is flowing
|
||||||
const prevScanIndex = scanIndex;
|
const hasEcgData = hasReceivedData && currentHR !== null;
|
||||||
scanIndex = (scanIndex + speed) % WAVE_BUFFER_SIZE;
|
const hasSpo2Data = hasReceivedData && currentSpO2 !== null;
|
||||||
|
const hasRespData = hasReceivedData && currentRR !== null;
|
||||||
|
|
||||||
// Increment procedural wave phases
|
if (hasEcgData || hasSpo2Data || hasRespData) {
|
||||||
// Scale phase speed depending on live vitals rate (beats/breaths per minute)
|
// Advance scan position
|
||||||
const hr = currentHR || 75; // fallback to 75 bpm
|
const prevScanIndex = scanIndex;
|
||||||
const rr = currentRR || 16; // fallback to 16 breaths
|
scanIndex = (scanIndex + speed) % WAVE_BUFFER_SIZE;
|
||||||
|
|
||||||
// Increment phase
|
const hr = currentHR || 75;
|
||||||
// For ECG/Pleth, 1 cycle is 1 heartbeat.
|
const rr = currentRR || 16;
|
||||||
// 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;
|
|
||||||
|
|
||||||
// Compute values and populate circular buffers for the newly scanned segment
|
const ecgPhaseInc = (hr / 60) * (speed / WAVE_BUFFER_SIZE) * 4.0;
|
||||||
for (let i = 0; i < speed; i++) {
|
const respPhaseInc = (rr / 60) * (speed / WAVE_BUFFER_SIZE) * 4.0;
|
||||||
const targetIdx = (prevScanIndex + i) % WAVE_BUFFER_SIZE;
|
|
||||||
|
|
||||||
ecgPhase += ecgPhaseInc / speed;
|
for (let i = 0; i < speed; i++) {
|
||||||
plethPhase += ecgPhaseInc / speed; // Sync SpO2 with ECG
|
const targetIdx = (prevScanIndex + i) % WAVE_BUFFER_SIZE;
|
||||||
respPhase += respPhaseInc / speed;
|
|
||||||
|
|
||||||
waveBuffers.ecg2[targetIdx] = getECGValue(ecgPhase);
|
ecgPhase += ecgPhaseInc / speed;
|
||||||
waveBuffers.ecg1[targetIdx] = getECG1Value(ecgPhase + 0.15); // Offset phase slightly for Lead I
|
plethPhase += ecgPhaseInc / speed;
|
||||||
waveBuffers.pleth[targetIdx] = getPlethValue(plethPhase - 0.2); // Pulse wave lag
|
respPhase += respPhaseInc / speed;
|
||||||
waveBuffers.resp[targetIdx] = getRespValue(respPhase);
|
|
||||||
|
// 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);
|
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) {
|
function varColor(varName) {
|
||||||
// Fallback colors if styling var fails
|
// Fallback colors if styling var fails
|
||||||
if (varName.includes('ecg-color')) return '#00ff66';
|
if (varName.includes('ecg-color')) return '#00ff66';
|
||||||
|
|||||||
Reference in New Issue
Block a user