Showcases how SciChart.js can be used in a Medical context, drawing ECGs with our High Performance JavaScript Charts
drawExample.ts
index.tsx
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 a real-time monitoring demo for vital signs such as ECG, blood pressure, blood volume, and blood oxygenation using SciChart.js integrated into a React application. The demo leverages the power of React hooks for state management and component lifecycle, providing a dynamic and interactive user experience.
The core of the implementation is based on the <SciChartReact/> component from the scichart-react library which initializes the chart using a dedicated draw function. This function configures the SciChartSurface by setting up axes, renderable series, and data series with FIFO sweeping for continuous real-time updates. The React component makes extensive use of useRef and useState to manage external chart controls and to subscribe to real-time data updates. The initialization callback subscribes to the data update events and properly starts the real-time data stream. Developers interested in building similar components can refer to Creating a SciChart React Component from the Ground Up for further guidance.
The demo showcases several key features including real-time data updates, efficient state management, and high-performance rendering using WebGL acceleration. Dynamic info panels update in sync with the chart data, illustrating how asynchronous data streams are handled natively in the React environment. The use of optimized data series with fifoCapacity and fifoSweeping mode enabled ensures smooth and continuous chart updates even under high data frequency, similar to the techniques described in Adding Realtime Updates | JavaScript Chart Documentation - SciChart.
Integration with React is achieved by employing best practices such as using React hooks for managing state and references to external library controls. The onInit callback initializes the chart and starts the live data updates while also returning a cleanup function to stop updates upon component unmounting, thereby preventing potential memory leaks. This approach of handling third-party libraries within React applications is explained in detail in React Charts with SciChart.js: Introducing “SciChart React” and further elaborated in Creating a React Dashboard with SciChart.js, SciChart-React and DeepSeek. Moreover, the example demonstrates how asynchronous data handling and event-driven updates can be neatly integrated with React's component lifecycle, ensuring an efficient, high-performance application.

Demonstrates Logarithmic Axis on a React 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 React Chart using SciChart.js, allowing data to overlap

See the frequency of recordings with the React 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 React Phasor Diagram example to combine a Cartesian surface with a Polar subsurface. Get seamless React integration with SciChart. View demo now.

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

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