JavaScript Vital Signs ECG/EKG Medical Demo

Showcases how SciChart.js can be used in a Medical context, drawing ECGs with our High Performance JavaScript Charts

ECG
0
V1 - 1.4MM
ST | +0.6 || +0.9
NIBP
AUTO
145/95
0/0
SV
ML 100
%**** 55
0.0
SPO2
18:06
71-
RESP
0

Fullscreen

Edit

 Edit

Docs

drawExample.ts

index.tsx

theme.ts

vitalSignsEcgData.ts

Copy to clipboard
Minimise
Fullscreen
1import {
2    CategoryAxis,
3    EllipsePointMarker,
4    EventHandler,
5    FastLineRenderableSeries,
6    NumberRange,
7    NumericAxis,
8    RightAlignedOuterVerticallyStackedAxisLayoutStrategy,
9    SciChartSurface,
10    XyDataSeries,
11} from "scichart";
12import { vitalSignsEcgData } from "./data/vitalSignsEcgData";
13import { appTheme } from "../../../theme";
14
15const STEP = 10;
16const TIMER_TIMEOUT_MS = 20;
17const STROKE_THICKNESS = 4;
18const POINTS_LOOP = 5200;
19const GAP_POINTS = 50;
20const DATA_LENGTH = vitalSignsEcgData.xValues.length;
21
22const { ecgHeartRateValues, bloodPressureValues, bloodVolumeValues, bloodOxygenationValues } = vitalSignsEcgData;
23
24// HELPER FUNCTIONS
25const getValuesFromData = (xIndex: number) => {
26    const xArr: number[] = [];
27    const ecgHeartRateArr: number[] = [];
28    const bloodPressureArr: number[] = [];
29    const bloodVolumeArr: number[] = [];
30    const bloodOxygenationArr: number[] = [];
31    for (let i = 0; i < STEP; i++) {
32        const dataIndex = (xIndex + i) % DATA_LENGTH;
33        const x = xIndex + i;
34        xArr.push(x);
35        ecgHeartRateArr.push(ecgHeartRateValues[dataIndex]);
36        bloodPressureArr.push(bloodPressureValues[dataIndex]);
37        bloodVolumeArr.push(bloodVolumeValues[dataIndex]);
38        bloodOxygenationArr.push(bloodOxygenationValues[dataIndex]);
39    }
40    return {
41        xArr,
42        ecgHeartRateArr,
43        bloodPressureArr,
44        bloodVolumeArr,
45        bloodOxygenationArr,
46    };
47};
48
49export type TDataUpdateInfo = {
50    ecg: number;
51    bloodPressure1: number;
52    bloodPressure2: number;
53    bloodVolume: number;
54    bloodOxygenation: number;
55};
56
57// SCICHART
58export const drawExample = async (rootElement: string | HTMLDivElement) => {
59    const { sciChartSurface, wasmContext } = await SciChartSurface.create(rootElement, {
60        theme: appTheme.SciChartJsTheme,
61    });
62
63    // Create a single, shared X-axis, pre-sized to fit the data in X, and is invisible
64
65    // Note: For fifoSweeping mode to work, the X-Axis must be a CategoryAxis
66    //      NumericAxis is also supported, but x-values must then be offsets from 0, ie do x % fifoCapacity.
67    //      See more info in the docs
68    const xAxis = new CategoryAxis(wasmContext, {
69        visibleRange: new NumberRange(0, POINTS_LOOP),
70        isVisible: false,
71    });
72    sciChartSurface.xAxes.add(xAxis);
73
74    // Create multiple y-axis, one per trace. Using the stacked vertically layout strategy
75    const yAxisHeartRate = new NumericAxis(wasmContext, {
76        id: "yHeartRate",
77        visibleRange: new NumberRange(0.7, 1.0),
78        isVisible: false,
79    });
80    const yAxisBloodPressure = new NumericAxis(wasmContext, {
81        id: "yBloodPressure",
82        visibleRange: new NumberRange(0.4, 0.8),
83        isVisible: false,
84    });
85    const yAxisBloodVolume = new NumericAxis(wasmContext, {
86        id: "yBloodVolume",
87        visibleRange: new NumberRange(0.1, 0.5),
88        isVisible: false,
89    });
90    const yAxisBloodOxygenation = new NumericAxis(wasmContext, {
91        id: "yBloodOxygenation",
92        visibleRange: new NumberRange(0, 0.2),
93        isVisible: false,
94    });
95    sciChartSurface.layoutManager!.rightOuterAxesLayoutStrategy =
96        new RightAlignedOuterVerticallyStackedAxisLayoutStrategy();
97    sciChartSurface.yAxes.add(yAxisHeartRate, yAxisBloodPressure, yAxisBloodVolume, yAxisBloodOxygenation);
98
99    // Using the NEW fifoCapacity, fifoSweeping mode in SciChart.js v3.2 we specify a number of points
100    // we want in the viewport. When the right edge of the viewport is reached, the series wraps around
101
102    const fifoSweepingGap = GAP_POINTS;
103    const dataSeries1 = new XyDataSeries(wasmContext, {
104        fifoCapacity: POINTS_LOOP,
105        fifoSweeping: true,
106        fifoSweepingGap,
107    });
108    const dataSeries2 = new XyDataSeries(wasmContext, {
109        fifoCapacity: POINTS_LOOP,
110        fifoSweeping: true,
111        fifoSweepingGap,
112    });
113    const dataSeries3 = new XyDataSeries(wasmContext, {
114        fifoCapacity: POINTS_LOOP,
115        fifoSweeping: true,
116        fifoSweepingGap,
117    });
118    const dataSeries4 = new XyDataSeries(wasmContext, {
119        fifoCapacity: POINTS_LOOP,
120        fifoSweeping: true,
121        fifoSweepingGap,
122    });
123
124    // A pointmarker with lastPointOnly = true will be used for all series to mark the last point
125    const pointMarkerOptions = {
126        width: 7,
127        height: 7,
128        strokeThickness: 2,
129        fill: appTheme.MutedSkyBlue,
130        lastPointOnly: true,
131    };
132
133    // Create four RenderableSeries which render the data
134    sciChartSurface.renderableSeries.add(
135        new FastLineRenderableSeries(wasmContext, {
136            yAxisId: yAxisHeartRate.id,
137            strokeThickness: STROKE_THICKNESS,
138            stroke: appTheme.VividOrange,
139            dataSeries: dataSeries1,
140            pointMarker: new EllipsePointMarker(wasmContext, { ...pointMarkerOptions, stroke: appTheme.VividOrange }),
141        })
142    );
143
144    sciChartSurface.renderableSeries.add(
145        new FastLineRenderableSeries(wasmContext, {
146            yAxisId: yAxisBloodPressure.id,
147            strokeThickness: STROKE_THICKNESS,
148            stroke: appTheme.VividSkyBlue,
149            dataSeries: dataSeries2,
150            pointMarker: new EllipsePointMarker(wasmContext, { ...pointMarkerOptions, stroke: appTheme.VividSkyBlue }),
151        })
152    );
153
154    sciChartSurface.renderableSeries.add(
155        new FastLineRenderableSeries(wasmContext, {
156            yAxisId: yAxisBloodVolume.id,
157            strokeThickness: STROKE_THICKNESS,
158            stroke: appTheme.VividPink,
159            dataSeries: dataSeries3,
160            pointMarker: new EllipsePointMarker(wasmContext, { ...pointMarkerOptions, stroke: appTheme.VividPink }),
161        })
162    );
163
164    sciChartSurface.renderableSeries.add(
165        new FastLineRenderableSeries(wasmContext, {
166            yAxisId: yAxisBloodOxygenation.id,
167            strokeThickness: STROKE_THICKNESS,
168            stroke: appTheme.VividTeal,
169            dataSeries: dataSeries4,
170            pointMarker: new EllipsePointMarker(wasmContext, { ...pointMarkerOptions, stroke: appTheme.VividTeal }),
171        })
172    );
173
174    const dataUpdateEventHandler = new EventHandler<TDataUpdateInfo>();
175
176    let timerId: NodeJS.Timeout;
177    let currentPoint = 0;
178
179    // The following code is run once per timer-step to update the data in the charts
180    // Here you would subsitute your own callback to receive data from your data feed or sensors
181    const runUpdateDataOnTimeout = () => {
182        // Get data
183        const { xArr, ecgHeartRateArr, bloodPressureArr, bloodVolumeArr, bloodOxygenationArr } =
184            getValuesFromData(currentPoint);
185        currentPoint += STEP;
186
187        // appendRange when fifoSweepingMode = true and fifoCapacity is reached will cause the series to wrap around
188        dataSeries1.appendRange(xArr, ecgHeartRateArr);
189        dataSeries2.appendRange(xArr, bloodPressureArr);
190        dataSeries3.appendRange(xArr, bloodVolumeArr);
191        dataSeries4.appendRange(xArr, bloodOxygenationArr);
192
193        // Update Info panel
194        if (currentPoint % 1000 === 0) {
195            const ecg = ecgHeartRateArr[STEP - 1];
196            const bloodPressure = bloodPressureArr[STEP - 1];
197            const bloodVolume = bloodVolumeArr[STEP - 1] + 3;
198            const bloodOxygenation = bloodOxygenationArr[STEP - 1];
199
200            const dataUpdateInfo = {
201                ecg: Math.floor(ecg * 20),
202                bloodPressure1: Math.floor(bloodPressure * 46),
203                bloodPressure2: Math.floor(bloodPressure * 31),
204                bloodVolume: bloodVolume + 8.6,
205                bloodOxygenation: Math.floor(bloodOxygenation * 10 + 93),
206            };
207            dataUpdateEventHandler.raiseEvent(dataUpdateInfo);
208        }
209        timerId = setTimeout(runUpdateDataOnTimeout, TIMER_TIMEOUT_MS);
210    };
211
212    const subscribeToDataUpdates = (handler: (info: TDataUpdateInfo) => void) => {
213        dataUpdateEventHandler.subscribe(handler);
214
215        // automatically cleanup subscription whe surface is deleted
216        sciChartSurface.addDeletable({ delete: () => dataUpdateEventHandler.unsubscribeAll() });
217    };
218
219    const stopUpdate = () => {
220        clearTimeout(timerId);
221        timerId = undefined;
222    };
223
224    const startUpdate = () => {
225        if (timerId) {
226            stopUpdate();
227        }
228        runUpdateDataOnTimeout();
229    };
230
231    return { sciChartSurface, subscribeToDataUpdates, controls: { startUpdate, stopUpdate } };
232};
233

Vital Signs Monitor Demo in JavaScript

Overview

This example, titled Vital Signs Monitor Demo, showcases how to build a high-performance real-time medical chart using SciChart.js with JavaScript. It simulates multiple medical signals including ECG, blood pressure, blood volume, and blood oxygenation, and is optimized for continuous data streaming and efficient updates.

Technical Implementation

The demo initializes a SciChartSurface with a hidden shared CategoryAxis for cyclic data management, which is essential for using FIFO-sweeping mode. Data is continuously appended to multiple XyDataSeries via the appendRange() method, ensuring smooth real-time updates. The use of fifoCapacity and fifoSweeping mode (see DataSeries Realtime Updates) allows the series to wrap around once the specified capacity is reached, optimizing performance. Additionally, a recursive setTimeout loop simulates real-time data streaming, while a custom EventHandler is used to update an information panel with computed metrics. For more detail on real-time updates, refer to Adding Realtime Updates.

Features and Capabilities

Real-Time Data Streaming: The demo employs a setTimeout loop to periodically fetch and append data points, demonstrating efficient real-time updating using JavaScript.

Performance Optimization: By configuring properties like fifoCapacity and fifoSweeping, the example highlights how FIFO buffers improve performance and simplify code for ECG style wrap-around charts when handling large volumes of data. For further insights, see the Performance Tips & Tricks guide.

Axis Configuration and Layout: The example uses a hidden CategoryAxis on the X-axis and multiple NumericAxis Y-axes that are vertically stacked with the RightAlignedOuterVerticallyStackedAxisLayoutStrategy. This approach simplifies the synchronization of multiple data series. More information is available in the Vertically Stacked Axis documentation.

Custom Event Handling and Memory Management: Custom event handlers are implemented using the EventHandler class to update the UI with the latest metrics. Additionally, sciChartSurface.addDeletable() is used to automatically clean up these subscriptions, ensuring optimal resource management during the chart's lifecycle.

Integration and Best Practices

Even though this demo is built using JavaScript, the underlying principles and performance optimizations can be applied across various frameworks. It emphasizes best practices for real-time data visualization including efficient data appending, optimized axis configurations, and sustainable memory management. By following these techniques, developers can create responsive, high-performance charting applications using SciChart.js.

javascript Chart Examples & Demos

See Also: Scientific & Medical Charts (10 Demos)

JavaScript Chart with Logarithmic Axis Example | SciChart

JavaScript Chart with Logarithmic Axis Example

Demonstrates Logarithmic Axis on a JavaScript Chart using SciChart.js. SciChart supports logarithmic axis with scientific or engineering notation and positive and negative values

LiDAR 3D Point Cloud of Geospatial Data | SciChart.js

LiDAR 3D Point Cloud of Geospatial Data

Demonstrating the capability of SciChart.js to create JavaScript 3D Point Cloud charts and visualize LiDAR data from the UK Defra Survey.

JavaScript Chart with Vertically Stacked Axes | SciChart

JavaScript Chart with Vertically Stacked Axes

Demonstrates Vertically Stacked Axes on a JavaScript Chart using SciChart.js, allowing data to overlap

Realtime Audio Spectrum Analyzer Chart | SciChart.js Demo

Realtime Audio Spectrum Analyzer Chart Example

See the frequency of recordings with the JavaScript audio spectrum analyzer example from SciChart. This real-time visualizer demo uses a Fourier Transform.

Realtime Audio Analyzer Bars Demo | SciChart.js Demo

Realtime Audio Analyzer Bars Demo

Demonstrating the capability of SciChart.js to create a JavaScript Audio Analyzer Bars and visualize the Fourier-Transform of an audio waveform in realtime.

Interactive Waterfall Chart | Javascript Charts | SciChart.js

Interactive Waterfall Spectral Chart

Demonstrates how to create a Waterfall chart in SciChart.js, showing chromotragraphy data with interactive selection of points.

Interactive Phasor Diagram chart | Javascript Charts | SciChart.js

Phasor Diagram Chart Example

See the JavaScript Phasor Diagram example to combine a Cartesian surface with a Polar subsurface. Get seamless JS integration with SciChart. View demo now.

NEW!
JavaScript Correlation Plot | Javascript Charts | SciChart.js

JavaScript Correlation Plot

Create JavaScript Correlation Plot with high performance SciChart.js. Easily render pre-defined point types. Supports custom shapes. Get your free trial now.

NEW!
Semiconductors Dashboard | JavaScript Charts | SciChart.js

Semiconductors Dashboard

JavaScript **Semiconductors Dashboard** using SciChart.js, by leveraging the **FastRectangleRenderableSeries**, and its `customTextureOptions` property to have a custom tiling texture fill.

NEW!
Wafer Analysis Chart | JavaScript Charts | SciChart.js

Wafer Analysis Chart

JavaScript **Wafer Analysis Chart** using SciChart.js, by leveraging the **FastRectangleRenderableSeries**, and crossfilter to enable live filtering.

SciChart Ltd, 16 Beaufort Court, Admirals Way, Docklands, London, E14 9XL.