Showcases how SciChart.js can be used in a Medical context, drawing ECGs with our High Performance JavaScript Charts
drawExample.ts
angular.ts
theme.ts
vitalSignsEcgData.ts
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};
233This example demonstrates an Angular implementation of a real-time medical monitoring chart using SciChart.js. The demo visualizes multiple vital signs data channels including ECG, blood pressure, blood volume, and blood oxygenation. By leveraging the ScichartAngularComponent provided by scichart-angular, it delivers high-performance chart rendering suitable for demanding real-time applications.
The Angular component initializes the SciChartSurface using SciChart.js’s API, configuring multiple axes and renderable series to display the various vital signs. Key aspects include the use of Angular lifecycle hooks as detailed in Component Lifecycle - Angular to manage initialization and cleanup, and dynamic styling through Angular’s ngStyle to create responsive info panels. Real-time data updates are implemented via a timer-based update mechanism with an event subscription model, ensuring that all data series and their corresponding info displays remain synchronized.
The demo supports real-time chart updates, dynamically plotting data from multiple channels with advanced WebGL rendering for smooth, high-frequency refreshes. It manages four separate renderable series, each corresponding to different vital signs, and updates detailed info panels using the Vertically Stacked Axis feature to reflect the latest measurements continuously. The use of dataSeries with fifoCapacity and fifoSweeping mode enable wrap-around charts, which is common when rendering ECG style applications. This provides an engaging and immediate visualization experience crucial for medical monitoring scenarios.
The implementation follows Angular best practices; it effectively incorporates Angular dependency injection and lifecycle management to integrate with third-party libraries like SciChart.js. Developers are encouraged to review Getting Started with SciChart JS and Tutorial 01 - Setting up a npm Project with SciChart.js for guidance on initial setup. Moreover, by utilizing robust event handling and efficient subscription management—as is further explained in Performance Optimization Techniques in Angular—this demo illustrates how Angular applications can integrate third-party tools effectively while maintaining optimal performance in a real-time data environment.

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

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

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

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

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

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

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

Create Angular Correlation Plot with high performance SciChart.js. Easily render pre-defined point types. Supports custom shapes. Get your free trial now.
Angular **Semiconductors Dashboard** using SciChart.js, by leveraging the **FastRectangleRenderableSeries**, and its `customTextureOptions` property to have a custom tiling texture fill.

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