Angular 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

angular.ts

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

See Also: Scientific & Medical Charts (5 Demos)

Angular Chart with Logarithmic Axis Example | SciChart.js Demo

Angular Chart with Logarithmic Axis Example

Demonstrates Logarithmic Axis on a Angular 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 Demo

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.

Angular Chart with Vertically Stacked Axes | SciChart.js Demo

Angular Chart with Vertically Stacked Axes

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

Realtime Audio Analyzer Demo | SciChart.js Demo

Realtime Audio Analyzer Demo

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

Interactive Waterfall Chart | SciChart.js Demo

Interactive Waterfall Spectral Chart

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

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