-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.js
More file actions
144 lines (125 loc) · 7.13 KB
/
Copy pathapp.js
File metadata and controls
144 lines (125 loc) · 7.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
const { useState, useEffect, useMemo } = React;
const { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, ReferenceLine } = Recharts;
// Simulating continuous data generation
const generateDataPoint = (time, baseHR) => {
// Basic sine wave + some noise to simulate ECG
const ecgVal = Math.sin(time / 100) * 1.5 + (Math.random() - 0.5) * 0.5;
// Simulate anomaly chance based on random factor
const isAnomaly = Math.random() < 0.05;
const hr = isAnomaly ? baseHR + (Math.random() * 40 + 20) : baseHR + (Math.random() * 5 - 2.5);
return {
time,
ecg: ecgVal,
hr: Math.round(hr),
anomaly: isAnomaly
}
}
const Dashboard = () => {
const [data, setData] = useState([]);
const [currentMetrics, setCurrentMetrics] = useState({ hr: 72, anomalyDetected: false });
const [timeCounter, setTimeCounter] = useState(0);
useEffect(() => {
// Init data
const initialData = [];
for(let i=0; i<50; i++) {
initialData.push(generateDataPoint(i*10, 72));
}
setData(initialData);
setTimeCounter(500);
// Simulate streaming
const interval = setInterval(() => {
setTimeCounter(prev => {
const newTime = prev + 10;
const newPoint = generateDataPoint(newTime, 72);
setData(currentData => {
const newData = [...currentData, newPoint];
if (newData.length > 50) newData.shift(); // keep last 50 points
return newData;
});
setCurrentMetrics({
hr: newPoint.hr,
anomalyDetected: newPoint.anomaly
});
return newTime;
});
}, 100); // 100ms updates
return () => clearInterval(interval);
}, []);
return (
<div className="min-h-screen p-8">
<header className="mb-8 flex justify-between items-center">
<div>
<h1 className="text-3xl font-bold text-white mb-2">AI-Driven Cardiac Monitor</h1>
<p className="text-slate-400">Real-time continuous ECG and anomaly detection</p>
</div>
<div className={`px-4 py-2 rounded flex items-center shadow-lg transition-colors ${currentMetrics.anomalyDetected ? 'bg-alert text-white animate-pulse' : 'bg-emerald-500 text-white'}`}>
<div className={`w-3 h-3 rounded-full mr-2 ${currentMetrics.anomalyDetected ? 'bg-white' : 'bg-white pulse-green'}`}></div>
<span className="font-semibold">{currentMetrics.anomalyDetected ? 'IRREGULAR RHYTHM DETECTED' : 'SYSTEM NORMAL'}</span>
</div>
</header>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Metric Cards */}
<div className="col-span-1 flex flex-col gap-6">
<div className="bg-cardbg rounded-xl p-6 shadow-xl border border-slate-700/50">
<h2 className="text-slate-400 text-sm font-semibold uppercase tracking-wider mb-2">Heart Rate</h2>
<div className="flex items-baseline">
<span className="text-5xl font-bold text-accent">{currentMetrics.hr}</span>
<span className="text-xl text-slate-500 ml-2">BPM</span>
</div>
<div className="mt-4 w-full bg-slate-800 rounded-full h-2">
<div className="bg-accent h-2 rounded-full transition-all duration-300" style={{width: `${(currentMetrics.hr / 200) * 100}%`}}></div>
</div>
</div>
<div className="bg-cardbg rounded-xl p-6 shadow-xl border border-slate-700/50">
<h2 className="text-slate-400 text-sm font-semibold uppercase tracking-wider mb-2">HRV (RMSSD)</h2>
<div className="flex items-baseline">
<span className="text-4xl font-bold text-emerald-400">42</span>
<span className="text-lg text-slate-500 ml-2">ms</span>
</div>
<p className="text-xs text-slate-400 mt-3 text-right">Healthy range</p>
</div>
<div className="bg-cardbg rounded-xl p-6 shadow-xl border border-slate-700/50">
<h2 className="text-slate-400 text-sm font-semibold uppercase tracking-wider mb-2">Resp Rate</h2>
<div className="flex items-baseline">
<span className="text-4xl font-bold text-purple-400">16</span>
<span className="text-lg text-slate-500 ml-2">breaths/min</span>
</div>
</div>
</div>
{/* Main Chart */}
<div className="col-span-1 lg:col-span-2 bg-cardbg rounded-xl p-6 shadow-xl border border-slate-700/50 flex flex-col">
<div className="flex justify-between items-end mb-4">
<h2 className="text-xl font-bold text-white">Live ECG Trace</h2>
<span className="text-xs text-slate-400 bg-slate-800 px-2 py-1 rounded">250Hz Simulation</span>
</div>
<div className="flex-grow w-full h-[400px]">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={data} margin={{ top: 5, right: 5, left: -20, bottom: 5 }}>
<CartesianGrid strokeDasharray="3 3" stroke="#334155" vertical={false} />
<XAxis dataKey="time" hide={true} />
<YAxis domain={[-3, 3]} stroke="#64748b" tick={{fill: '#64748b'}} />
<Tooltip
contentStyle={{backgroundColor: '#0f172a', border: '1px solid #334155'}}
itemStyle={{color: '#38bdf8'}}
/>
<Line
type="monotone"
dataKey="ecg"
stroke={currentMetrics.anomalyDetected ? '#ef4444' : '#38bdf8'}
strokeWidth={3}
dot={false}
isAnimationActive={false} // Disable to make streaming look smooth
/>
</LineChart>
</ResponsiveContainer>
</div>
</div>
</div>
<div className="mt-8 text-center text-slate-500 text-sm">
AI-Driven Wearable Cardiac Monitoring Prototype • Running on local browser inference simulator
</div>
</div>
);
};
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Dashboard />);