+```
+
+### 4. Error Handling
+The component displays connection errors automatically. No additional error handling needed.
+
+## Medical Disclaimer
+
+⚠️ **IMPORTANT**: This component is for **visualization purposes only** and is **NOT intended for medical diagnosis or treatment**.
+
+- Do not use for clinical decision-making
+- Do not use as a replacement for medical-grade equipment
+- Always consult qualified healthcare professionals
+- This is educational/demo software, not medical device software
+
+## Browser Compatibility
+
+- Chrome/Edge: ✅ Full support
+- Firefox: ✅ Full support
+- Safari: ✅ Full support
+- Mobile browsers: ✅ Full support (with reduced FPS on older devices)
+
+Requires:
+- HTML5 Canvas support
+- ES6+ JavaScript
+- WebSocket support (for realtime mode)
+
+## Troubleshooting
+
+### No waveform displayed
+- Check that `dataPath` matches your data structure
+- Verify WebSocket connection is active (check connection indicator)
+- Ensure ECG values are in millivolts (mV), typically -2 to +2 range
+
+### Heart rate shows "--"
+- Need at least 2 heartbeats for calculation
+- Check that R-peaks are detectable (gain may be too low)
+- Ensure sampling rate is configured correctly
+
+### Poor performance
+- Reduce `samplingRate` if not using medical-grade precision
+- Reduce `bufferDuration` to use less memory
+- Disable `showHeartRate` if not needed (saves CPU)
+
+## Examples
+
+### Multiple Patients Dashboard
+```tsx
+import { ECGChart, Facility, Device } from 'scadable/health';
+
+function ICUDashboard() {
+ const facility = new Facility('your-api-key');
+
+ return (
+
+
+
+
+ );
+}
+```
+
+### With Alert System
+```tsx
+function ECGWithAlerts() {
+ const handleHeartRateChange = (bpm: number) => {
+ if (bpm > 120) {
+ console.error('Tachycardia detected!');
+ // Trigger alert system
+ } else if (bpm < 50) {
+ console.error('Bradycardia detected!');
+ // Trigger alert system
+ }
+ };
+
+ return (
+
+ );
+}
+```
+
+## Related Components
+
+- [EEGChart](./EEGChart.md) - Multi-channel brain wave visualization
+- [LiveTelemetryLineChart](./LiveTelemetryLineChart.md) - General-purpose line chart
+
+## Support
+
+For issues or questions:
+- GitHub Issues: https://github.com/scadable/library-react/issues
+- Documentation: https://github.com/scadable/library-react
diff --git a/docs/EEGChart.md b/docs/EEGChart.md
new file mode 100644
index 0000000..1b70eb0
--- /dev/null
+++ b/docs/EEGChart.md
@@ -0,0 +1,522 @@
+# EEGChart Component
+
+A real-time electroencephalogram (EEG) visualization component for multi-channel brain wave monitoring with optional spectral analysis and customizable display modes.
+
+## Medical Context
+
+An **EEG (Electroencephalogram)** records electrical activity in the brain using multiple electrodes placed on the scalp. EEG is used to:
+
+- Diagnose epilepsy and seizure disorders
+- Study sleep patterns and disorders
+- Monitor brain activity during surgery
+- Research cognitive processes and brain states
+- Detect brain injuries and abnormalities
+
+### Frequency Bands
+
+Brain waves are categorized into five main frequency bands:
+
+| Band | Frequency | Mental State |
+|------|-----------|--------------|
+| **Delta (δ)** | 0.5-4 Hz | Deep sleep, unconscious |
+| **Theta (θ)** | 4-8 Hz | Drowsiness, meditation, creativity |
+| **Alpha (α)** | 8-13 Hz | Relaxed, calm, awake |
+| **Beta (β)** | 13-30 Hz | Active thinking, concentration, alertness |
+| **Gamma (γ)** | 30-100 Hz | High-level information processing |
+
+## Installation
+
+```bash
+npm install scadable
+```
+
+## Import
+
+```typescript
+import { EEGChart, EEGChannelConfig } from 'scadable/health';
+```
+
+## Basic Usage
+
+### Real-time 4-Channel EEG
+
+```tsx
+import { EEGChart, EEGChannelConfig } from 'scadable/health';
+
+function MyComponent() {
+ const channels: EEGChannelConfig[] = [
+ { name: 'Fp1', dataPath: '.data.channels.Fp1', color: '#3b82f6' },
+ { name: 'Fp2', dataPath: '.data.channels.Fp2', color: '#10b981' },
+ { name: 'C3', dataPath: '.data.channels.C3', color: '#f59e0b' },
+ { name: 'C4', dataPath: '.data.channels.C4', color: '#ef4444' },
+ ];
+
+ return (
+
+ );
+}
+```
+
+### With Spectral Analysis
+
+```tsx
+import { EEGChart, EEGChannelConfig } from 'scadable/health';
+
+function MyComponent() {
+ const channels: EEGChannelConfig[] = [
+ { name: 'Fp1', dataPath: '.data.channels.Fp1', color: '#3b82f6' },
+ { name: 'Fp2', dataPath: '.data.channels.Fp2', color: '#10b981' },
+ ];
+
+ return (
+
+ );
+}
+```
+
+### Historical Mode
+
+```tsx
+import { EEGChart, EEGChannelConfig } from 'scadable/health';
+
+const channels: EEGChannelConfig[] = [
+ { name: 'Fp1', dataPath: '.data.channels.Fp1', color: '#3b82f6' },
+ { name: 'Fp2', dataPath: '.data.channels.Fp2', color: '#10b981' },
+];
+
+const historicalData = new Map([
+ ['Fp1', [12.5, 15.3, 18.2, ...]],
+ ['Fp2', [10.1, 13.8, 16.5, ...]],
+]);
+
+function MyComponent() {
+ return (
+
+ );
+}
+```
+
+## Props Reference
+
+### Connection Props
+
+| Prop | Type | Required | Description |
+|------|------|----------|-------------|
+| `apiKey` | `string` | Conditional* | API key for authentication |
+| `deviceId` | `string` | Conditional* | Device ID to connect to |
+| `device` | `Device` | Conditional* | Pre-configured Device instance |
+
+*Either provide `device` OR both `apiKey` and `deviceId` (for realtime mode only)
+
+### Data Props
+
+| Prop | Type | Default | Description |
+|------|------|---------|-------------|
+| `channels` | `EEGChannelConfig[]` | Required | Channel configurations (see below) |
+| `mode` | `'realtime' \| 'historical'` | `'realtime'` | Display mode |
+| `historicalData` | `Map` | - | Pre-recorded channel data for historical mode |
+
+### Display Props
+
+| Prop | Type | Default | Description |
+|------|------|---------|-------------|
+| `title` | `string` | `'EEG Monitor'` | Chart title |
+| `width` | `number` | `1000` | Chart width in pixels |
+| `height` | `number` | `600` | Chart height in pixels |
+| `layout` | `'stacked' \| 'overlay'` | `'stacked'` | Channel display layout |
+| `showLabels` | `boolean` | `true` | Show channel labels |
+
+### Medical Settings
+
+| Prop | Type | Default | Description |
+|------|------|---------|-------------|
+| `sensitivity` | `number` | `70` | Sensitivity in µV/mm (standard: 5-100) |
+| `timeWindow` | `number` | `10` | Time window in seconds |
+| `samplingRate` | `number` | `256` | Sampling rate in Hz (typical: 256-512) |
+
+### Spectral Analysis
+
+| Prop | Type | Default | Description |
+|------|------|---------|-------------|
+| `showSpectralAnalysis` | `boolean` | `false` | Show spectral analysis panel |
+| `frequencyBands` | `Array<'delta' \| 'theta' \| 'alpha' \| 'beta' \| 'gamma'>` | All bands | Which frequency bands to analyze |
+
+### Color Customization
+
+| Prop | Type | Default | Description |
+|------|------|---------|-------------|
+| `backgroundColor` | `string` | `'#ffffff'` | Background color |
+| `gridColor` | `string` | `'#e5e7eb'` | Grid line color |
+| `textColor` | `string` | `'#1f2937'` | Text color |
+
+## EEGChannelConfig
+
+```typescript
+interface EEGChannelConfig {
+ name: string; // Channel name (e.g., "Fp1", "C3")
+ dataPath: string; // JSON path to extract value
+ color: string; // Channel display color
+}
+```
+
+## Channel Naming (10-20 System)
+
+The international 10-20 system standardizes electrode placement:
+
+- **Fp**: Frontal pole (Fp1, Fp2)
+- **F**: Frontal (F3, F4, F7, F8, Fz)
+- **C**: Central (C3, C4, Cz)
+- **T**: Temporal (T3, T4, T5, T6)
+- **P**: Parietal (P3, P4, Pz)
+- **O**: Occipital (O1, O2)
+
+Odd numbers = left hemisphere, Even numbers = right hemisphere, z = midline
+
+## Expected Data Format
+
+The component expects WebSocket data in this format:
+
+```json
+{
+ "broker_id": "service-mqtt-...",
+ "device_id": "eeg-device-001",
+ "timestamp": "2025-11-06T19:54:50.802Z",
+ "data": {
+ "channels": {
+ "Fp1": 45.2,
+ "Fp2": 42.8,
+ "C3": 38.5,
+ "C4": 40.1
+ }
+ }
+}
+```
+
+Where channel values are in **microvolts (µV)**.
+
+## Layout Modes
+
+### Stacked Layout
+- Each channel has its own horizontal baseline
+- Easy to identify individual channels
+- Best for monitoring multiple channels
+- Default and most common mode
+
+```tsx
+
+```
+
+### Overlay Layout
+- All channels share the same baseline
+- Easy to compare waveform synchronization
+- Useful for correlation analysis
+- Better for fewer channels (2-4)
+
+```tsx
+
+```
+
+## Spectral Analysis
+
+When `showSpectralAnalysis={true}`, the component:
+1. Performs FFT (Fast Fourier Transform) on each channel
+2. Calculates power in each frequency band
+3. Identifies the dominant frequency band
+4. Updates analysis every second
+5. Displays results in a side panel
+
+### Interpreting Results
+
+```tsx
+
+```
+
+Example output:
+- **High Alpha**: Relaxed, meditative state
+- **High Beta**: Active thinking, concentration
+- **High Theta**: Drowsy, creative state
+- **High Delta**: Deep sleep
+
+## Sensitivity Settings
+
+Sensitivity controls the vertical scale (µV per mm):
+
+| Sensitivity | Use Case |
+|-------------|----------|
+| **30 µV/mm** | High amplification, small signals |
+| **50 µV/mm** | Standard clinical setting |
+| **70 µV/mm** | Default, balanced view |
+| **100 µV/mm** | Lower amplification, large signals |
+
+```tsx
+// High sensitivity for small signals
+
+
+// Low sensitivity for large signals
+
+```
+
+## Color Customization Examples
+
+### Standard Medical
+```tsx
+
+```
+
+### Dark Mode
+```tsx
+
+```
+
+### High Contrast
+```tsx
+
+```
+
+### Per-Channel Colors
+```tsx
+const channels: EEGChannelConfig[] = [
+ { name: 'Fp1', dataPath: '.data.channels.Fp1', color: '#FF0000' },
+ { name: 'Fp2', dataPath: '.data.channels.Fp2', color: '#00FF00' },
+ { name: 'C3', dataPath: '.data.channels.C3', color: '#0000FF' },
+ { name: 'C4', dataPath: '.data.channels.C4', color: '#FFFF00' },
+];
+```
+
+## Performance
+
+- Supports up to **1024 Hz** sampling rate
+- Renders at **60 FPS** using hardware-accelerated Canvas
+- Efficiently handles 8+ channels simultaneously
+- Memory efficient with per-channel circular buffers
+- Spectral analysis runs in background without blocking rendering
+
+## Best Practices
+
+### 1. Choose Appropriate Layout
+```tsx
+// For 4+ channels, use stacked
+
+
+// For 2-3 channels comparison, use overlay
+
+```
+
+### 2. Configure Sensitivity Based on Signal
+```tsx
+// For adult scalp EEG (typical 50-100 µV)
+
+
+// For pediatric EEG (higher amplitude)
+
+```
+
+### 3. Use Descriptive Channel Names
+```tsx
+const channels: EEGChannelConfig[] = [
+ { name: 'Left Frontal', dataPath: '.data.channels.Fp1', color: '#3b82f6' },
+ { name: 'Right Frontal', dataPath: '.data.channels.Fp2', color: '#10b981' },
+];
+```
+
+### 4. Responsive Design
+```tsx
+
+```
+
+## Common Montages
+
+### Bipolar Montage (Double Banana)
+```tsx
+const channels: EEGChannelConfig[] = [
+ { name: 'Fp1-F3', dataPath: '.data.bipolar.Fp1_F3', color: '#3b82f6' },
+ { name: 'F3-C3', dataPath: '.data.bipolar.F3_C3', color: '#10b981' },
+ { name: 'C3-P3', dataPath: '.data.bipolar.C3_P3', color: '#f59e0b' },
+ { name: 'P3-O1', dataPath: '.data.bipolar.P3_O1', color: '#ef4444' },
+];
+```
+
+### Referential Montage
+```tsx
+const channels: EEGChannelConfig[] = [
+ { name: 'Fp1-A1', dataPath: '.data.referential.Fp1', color: '#3b82f6' },
+ { name: 'Fp2-A2', dataPath: '.data.referential.Fp2', color: '#10b981' },
+ { name: 'C3-A1', dataPath: '.data.referential.C3', color: '#f59e0b' },
+ { name: 'C4-A2', dataPath: '.data.referential.C4', color: '#ef4444' },
+];
+```
+
+## Medical Disclaimer
+
+⚠️ **IMPORTANT**: This component is for **visualization purposes only** and is **NOT intended for medical diagnosis or treatment**.
+
+- Do not use for clinical decision-making
+- Do not use as a replacement for medical-grade EEG equipment
+- Always consult qualified healthcare professionals
+- This is educational/demo software, not medical device software
+- Not approved by FDA or other regulatory agencies
+
+## Browser Compatibility
+
+- Chrome/Edge: ✅ Full support
+- Firefox: ✅ Full support
+- Safari: ✅ Full support
+- Mobile browsers: ✅ Full support (may have reduced performance with 8+ channels)
+
+Requires:
+- HTML5 Canvas support
+- ES6+ JavaScript
+- WebSocket support (for realtime mode)
+
+## Troubleshooting
+
+### Channels not displaying
+- Verify `dataPath` matches your data structure
+- Check WebSocket connection (connection indicator)
+- Ensure channel values are in microvolts (µV), typically 0-100 range
+
+### Waveforms too small/large
+- Adjust `sensitivity` prop (lower = larger waveform)
+- Check that values are in µV, not mV or V
+
+### Spectral analysis not updating
+- Ensure enough data is collected (need at least 1 second of data)
+- Check `samplingRate` matches your actual data rate
+- Verify `showSpectralAnalysis={true}` is set
+
+### Poor performance with many channels
+- Reduce `samplingRate` if medical precision not required
+- Reduce `timeWindow` to use less memory
+- Disable `showSpectralAnalysis` if not needed
+- Use fewer channels (8 or less recommended)
+
+## Examples
+
+### Sleep Study Monitor
+```tsx
+import { EEGChart, EEGChannelConfig } from 'scadable/health';
+
+function SleepStudy() {
+ const channels: EEGChannelConfig[] = [
+ { name: 'C3', dataPath: '.data.channels.C3', color: '#3b82f6' },
+ { name: 'C4', dataPath: '.data.channels.C4', color: '#10b981' },
+ ];
+
+ return (
+
+ );
+}
+```
+
+### Full Clinical Montage
+```tsx
+function ClinicalEEG() {
+ const channels: EEGChannelConfig[] = [
+ { name: 'Fp1', dataPath: '.data.channels.Fp1', color: '#3b82f6' },
+ { name: 'Fp2', dataPath: '.data.channels.Fp2', color: '#10b981' },
+ { name: 'F3', dataPath: '.data.channels.F3', color: '#f59e0b' },
+ { name: 'F4', dataPath: '.data.channels.F4', color: '#ef4444' },
+ { name: 'C3', dataPath: '.data.channels.C3', color: '#8b5cf6' },
+ { name: 'C4', dataPath: '.data.channels.C4', color: '#ec4899' },
+ { name: 'P3', dataPath: '.data.channels.P3', color: '#06b6d4' },
+ { name: 'P4', dataPath: '.data.channels.P4', color: '#84cc16' },
+ { name: 'O1', dataPath: '.data.channels.O1', color: '#f97316' },
+ { name: 'O2', dataPath: '.data.channels.O2', color: '#14b8a6' },
+ ];
+
+ return (
+
+ );
+}
+```
+
+### Meditation Monitor
+```tsx
+function MeditationMonitor() {
+ const channels: EEGChannelConfig[] = [
+ { name: 'Fp1', dataPath: '.data.channels.Fp1', color: '#3b82f6' },
+ ];
+
+ return (
+
+
Meditation Session
+
+
High Alpha = Deep relaxation, High Theta = Meditative state
+
+ );
+}
+```
+
+## Related Components
+
+- [ECGChart](./ECGChart.md) - Heart electrical activity visualization
+- [LiveTelemetryLineChart](./LiveTelemetryLineChart.md) - General-purpose line chart
+
+## Support
+
+For issues or questions:
+- GitHub Issues: https://github.com/scadable/library-react/issues
+- Documentation: https://github.com/scadable/library-react
diff --git a/docs/LiveTelemetryGage.md b/docs/LiveTelemetryGage.md
new file mode 100644
index 0000000..4334c4a
--- /dev/null
+++ b/docs/LiveTelemetryGage.md
@@ -0,0 +1,393 @@
+# LiveTelemetryGage
+
+A modern, animated circular gauge component for displaying real-time telemetry data from WebSocket connections. Perfect for SCADA systems, IoT dashboards, and monitoring applications that need at-a-glance status indicators.
+
+## Features
+
+- 🎯 **Real-time gauge visualization** - Displays live streaming data on a circular gauge
+- 🌈 **Dynamic color transitions** - Smooth color changes from green (low) to yellow (mid) to red (high)
+- ✨ **Modern design** - Sleek, minimalist SVG-based gauge with smooth animations
+- 📊 **Customizable ranges** - Set min/max values to match your sensor specifications
+- 🎨 **Custom color schemes** - Configure colors for different value ranges
+- 🔢 **Flexible formatting** - Control decimal places and unit labels
+- 🟢 **Live status indicator** - Visual connection status badge
+- 🔌 **WebSocket powered** - Real-time data through WebSocket connections
+
+## Installation
+
+```bash
+npm install scadable
+```
+
+## Basic Usage
+
+### Simple Example
+
+```tsx
+import { LiveTelemetryGage } from 'scadable';
+
+function TemperatureGauge() {
+ return (
+
+ );
+}
+```
+
+### Using a Device Instance
+
+For more control, you can pass a pre-configured `Device` instance:
+
+```tsx
+import { Facility, Device, LiveTelemetryGage } from 'scadable';
+
+function PressureGauge() {
+ const facility = new Facility('your-api-key');
+ const device = new Device(facility, 'your-device-id');
+
+ return (
+
+ );
+}
+```
+
+## Understanding JSON Paths
+
+The gauge uses **JSON path notation** to extract data from the WebSocket payload. Given this example payload:
+
+```json
+{
+ "broker_id": "service-mqtt-6446d94bf6-pn8x7",
+ "device_id": "mifKUN32sahJNOvo",
+ "payload": "Mw==",
+ "qos": 0,
+ "timestamp": "2025-11-06T19:54:50.802707085Z",
+ "topic": "sensors/temperature",
+ "data": {
+ "temperature": 72.5,
+ "pressure": 145.3,
+ "rpm": 3500
+ }
+}
+```
+
+You would configure:
+
+- **dataPath**: `".data.temperature"` - Extracts the temperature from the nested data object
+
+### Path Examples
+
+| Path | Extracts | Value |
+|------|----------|-------|
+| `".data.temperature"` | Nested temperature | `72.5` |
+| `".data.pressure"` | Nested pressure | `145.3` |
+| `".data.rpm"` | Nested RPM | `3500` |
+| `".qos"` | Quality of service | `0` |
+
+## Props Reference
+
+### Required Props
+
+| Prop | Type | Description |
+|------|------|-------------|
+| `title` | `string` | Gauge title displayed at the top |
+| `dataPath` | `string` | JSON path to extract value (e.g., `".data.temperature"`) |
+| `min` | `number` | Minimum range value (gauge starts here) |
+| `max` | `number` | Maximum range value (gauge ends here) |
+
+### Connection Props (Choose One)
+
+Either provide a Device instance OR apiKey + deviceId:
+
+| Prop | Type | Description |
+|------|------|-------------|
+| `device` | `Device` | Pre-configured Device instance |
+| `apiKey` | `string` | API key for authentication |
+| `deviceId` | `string` | Device ID to connect to |
+
+### Optional Props
+
+| Prop | Type | Default | Description |
+|------|------|---------|-------------|
+| `unit` | `string` | `""` | Unit label (e.g., "°C", "PSI", "RPM") |
+| `decimals` | `number` | `1` | Number of decimal places to display |
+| `size` | `number` | `300` | Gauge width and height in pixels |
+| `colorLow` | `string` | `"#22c55e"` | Color for low values (green) |
+| `colorMid` | `string` | `"#eab308"` | Color for medium values (yellow) |
+| `colorHigh` | `string` | `"#ef4444"` | Color for high values (red) |
+
+## Advanced Examples
+
+### Temperature Gauge with Custom Range
+
+```tsx
+
+```
+
+### Pressure Gauge with Custom Colors
+
+```tsx
+
+```
+
+### RPM Gauge (No Decimals)
+
+```tsx
+
+```
+
+### Multiple Gauges Dashboard
+
+```tsx
+import { LiveTelemetryGage } from 'scadable';
+
+function Dashboard() {
+ return (
+
+
+
+
+
+
+
+ );
+}
+```
+
+### Compact Gauges for Space-Constrained UIs
+
+```tsx
+
+```
+
+## Best Practices
+
+### Setting Min/Max Range
+
+Choose `min` and `max` based on your sensor specifications:
+
+- ✅ **Good**: `min={0}`, `max={100}` for 0-100°C temperature sensor
+- ✅ **Good**: `min={0}`, `max={200}` for 0-200 PSI pressure sensor
+- ✅ **Good**: `min={0}`, `max={10000}` for 0-10k RPM motor
+- ❌ **Bad**: `min={0}`, `max={1000}` for data that only goes 0-50 (gauge mostly empty)
+
+### Decimal Places
+
+Balance between precision and readability:
+
+- **0 decimals**: Good for RPM, counts, large numbers (e.g., `3500 RPM`)
+- **1 decimal**: Good for temperature, pressure (e.g., `72.5 °C`)
+- **2 decimals**: Good for precise measurements (e.g., `3.14 V`)
+
+### Gauge Size
+
+Choose size based on your layout:
+
+- **180-200px**: Compact, for multi-gauge dashboards
+- **250-300px**: Standard, good for most use cases (default: 300)
+- **320-400px**: Large, for primary/critical metrics
+
+### Color Schemes
+
+#### Standard (Traffic Light)
+```tsx
+colorLow="#22c55e" // Green (good)
+colorMid="#eab308" // Yellow (warning)
+colorHigh="#ef4444" // Red (danger)
+```
+
+#### Cool (Blue to Pink)
+```tsx
+colorLow="#3b82f6" // Blue
+colorMid="#8b5cf6" // Purple
+colorHigh="#ec4899" // Pink
+```
+
+#### Monochrome (Single Color Intensity)
+```tsx
+colorLow="#cbd5e1" // Light gray
+colorMid="#64748b" // Medium gray
+colorHigh="#1e293b" // Dark gray
+```
+
+## Color Transition Logic
+
+The gauge automatically changes color based on the current value percentage:
+
+- **0-33%** of range: Uses `colorLow`
+- **34-66%** of range: Uses `colorMid`
+- **67-100%** of range: Uses `colorHigh`
+
+For example, with `min={0}` and `max={100}`:
+- Value 25: Green (`colorLow`)
+- Value 50: Yellow (`colorMid`)
+- Value 85: Red (`colorHigh`)
+
+## Visual Design
+
+The gauge includes:
+
+- **Circular arc** (270° sweep from bottom-left to bottom-right)
+- **Large centered value** with dynamic color
+- **Unit label** below the value
+- **Min/Max labels** at arc endpoints
+- **Range display** below the gauge
+- **Live status badge** (top-right corner with pulsing indicator)
+- **Smooth transitions** (0.5s ease-out animations)
+- **Drop shadow** on the active arc for depth
+
+## Troubleshooting
+
+### Gauge not displaying data
+
+1. Verify your API key and device ID are correct
+2. Check that the WebSocket connection shows "Live" status
+3. Verify JSON path matches your payload structure
+4. Check browser console for errors
+5. Ensure the extracted value is a number
+
+### Gauge shows "--"
+
+- No data received yet (connection establishing)
+- JSON path doesn't match payload structure
+- Value at path is not a valid number
+
+### Range issues
+
+- Ensure `min < max`
+- Set range to match sensor specifications
+- Value outside range will be clamped to 0% or 100%
+
+### Colors not changing
+
+- Verify `colorLow`, `colorMid`, `colorHigh` are valid CSS colors
+- Colors transition at 33% and 67% boundaries
+- Check that values are in expected range
+
+## Accessibility
+
+The gauge component:
+
+- Uses semantic HTML and ARIA-compliant SVG
+- Includes text labels for screen readers
+- Displays numerical value prominently
+- Shows connection status visually and textually
+
+## Performance
+
+- Lightweight SVG-based rendering
+- Smooth CSS transitions (no JavaScript animation loops)
+- Efficient WebSocket data handling
+- Minimal re-renders with React hooks
+
+## TypeScript Support
+
+The component is fully typed. Import types:
+
+```tsx
+import type { LiveTelemetryGageProps } from 'scadable';
+
+const gaugeProps: LiveTelemetryGageProps = {
+ device: myDevice,
+ title: "My Gauge",
+ dataPath: ".data.value",
+ min: 0,
+ max: 100,
+ unit: "V",
+ decimals: 2,
+ size: 300,
+};
+```
+
+## See Also
+
+- [Facility Class](./Facility.md)
+- [Device Class](./Device.md)
+- [useLiveTelemetry Hook](./useLiveTelemetry.md)
+- [LiveTelemetryLineChart Component](./LiveTelemetryLineChart.md)
diff --git a/package.json b/package.json
index a854d09..854dad6 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,11 @@
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.js"
+ },
+ "./health": {
+ "types": "./dist/health.d.ts",
+ "import": "./dist/health.mjs",
+ "require": "./dist/health.js"
}
},
"scripts": {
diff --git a/src/components/ECGChart.stories.tsx b/src/components/ECGChart.stories.tsx
new file mode 100644
index 0000000..4edf1db
--- /dev/null
+++ b/src/components/ECGChart.stories.tsx
@@ -0,0 +1,284 @@
+import React from 'react';
+import type { Meta, StoryObj } from '@storybook/react';
+import { ECGChart } from './ECGChart';
+import { Device } from '../core/Device';
+import { Facility } from '../core/Facility';
+
+// Mock ECG Device that generates realistic ECG waveform with P-QRS-T pattern
+class MockECGDevice extends Device {
+ private mockInterval: ReturnType | null = null;
+ private sampleIndex: number = 0;
+ private heartRate: number = 75; // BPM
+
+ constructor(facility: Facility, deviceId: string, heartRate: number = 75) {
+ super(facility, deviceId);
+ this.heartRate = heartRate;
+ }
+
+ // Generate realistic ECG waveform (P-QRS-T pattern)
+ private generateECGValue(): number {
+ const samplesPerBeat = (60 / this.heartRate) * 250; // 250 Hz sampling
+ const position = (this.sampleIndex % samplesPerBeat) / samplesPerBeat;
+
+ let value = 0;
+
+ // P wave (atrial depolarization) - small bump at ~0.15
+ if (position >= 0.12 && position <= 0.18) {
+ const pPosition = (position - 0.12) / 0.06;
+ value += 0.15 * Math.sin(pPosition * Math.PI);
+ }
+
+ // QRS complex (ventricular depolarization) - sharp spike at ~0.35
+ if (position >= 0.32 && position <= 0.42) {
+ const qrsPosition = (position - 0.32) / 0.1;
+ if (qrsPosition < 0.2) {
+ // Q wave (small dip)
+ value += -0.1 * Math.sin(qrsPosition * 5 * Math.PI);
+ } else if (qrsPosition < 0.6) {
+ // R wave (tall spike)
+ value += 1.2 * Math.sin((qrsPosition - 0.2) * 2.5 * Math.PI);
+ } else {
+ // S wave (small dip)
+ value += -0.15 * Math.sin((qrsPosition - 0.6) * 2.5 * Math.PI);
+ }
+ }
+
+ // T wave (ventricular repolarization) - rounded bump at ~0.6
+ if (position >= 0.52 && position <= 0.68) {
+ const tPosition = (position - 0.52) / 0.16;
+ value += 0.25 * Math.sin(tPosition * Math.PI);
+ }
+
+ // Add small baseline noise
+ value += (Math.random() - 0.5) * 0.02;
+
+ this.sampleIndex++;
+ return value;
+ }
+
+ connect(): void {
+ setTimeout(() => {
+ (this as any).updateConnectionStatus('connected');
+
+ // Send ECG data at 250 Hz (4ms intervals)
+ this.mockInterval = setInterval(() => {
+ const ecgValue = this.generateECGValue();
+ const mockPayload = {
+ broker_id: "service-mqtt-mock-ecg",
+ device_id: this.deviceId,
+ payload: "ecg",
+ qos: 0,
+ timestamp: new Date().toISOString(),
+ topic: "health/ecg",
+ data: {
+ ecg: parseFloat(ecgValue.toFixed(3)),
+ },
+ };
+ (this as any).handleMessage(JSON.stringify(mockPayload));
+ }, 4); // 4ms = 250 Hz
+ }, 500);
+ }
+
+ disconnect(): void {
+ if (this.mockInterval) {
+ clearInterval(this.mockInterval);
+ }
+ (this as any).updateConnectionStatus('disconnected');
+ }
+}
+
+const meta: Meta = {
+ title: 'Health/ECGChart',
+ component: ECGChart,
+ tags: ['autodocs'],
+ parameters: {
+ docs: {
+ description: {
+ component: 'Real-time ECG (electrocardiogram) visualization component with medical-standard grid and heart rate calculation. Displays P-QRS-T waveform pattern with customizable colors and settings.',
+ },
+ },
+ },
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+const mockFacility = new Facility('storybook-api-key');
+const mockDevice1 = new MockECGDevice(mockFacility, 'ecg-device-1', 75);
+const mockDevice2 = new MockECGDevice(mockFacility, 'ecg-device-2', 90);
+const mockDevice3 = new MockECGDevice(mockFacility, 'ecg-device-3', 60);
+
+export const StandardECG: Story = {
+ args: {
+ device: mockDevice1,
+ title: 'ECG Monitor - Standard Settings',
+ dataPath: '.data.ecg',
+ mode: 'realtime',
+ sweepSpeed: 25,
+ gain: 10,
+ samplingRate: 250,
+ showGrid: true,
+ showCalibration: true,
+ showHeartRate: true,
+ width: 800,
+ height: 400,
+ },
+ render: (args) => (
+
+
+ Standard ECG display with 25mm/s sweep speed and 10mm/mV gain. Watch the realistic P-QRS-T waveform pattern and live heart rate calculation.
+
+
+
+
ECG Waveform Components:
+
+
P wave: Atrial depolarization (small bump before main spike)