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};
233
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
Demonstrating the capability of SciChart.js to create a JavaScript Audio Analyzer 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.