SciChart® the market leader in Fast WPF Charts, WPF 3D Charts, iOS Chart, Android Chart and JavaScript Chart Components
SciChart Android ships with ~90Â Android Chart Examples which you can browse, play with, view the source-code and even export each SciChart Android Chart Example to a stand-alone Android Studio project. All of this is possible with the new and improved SciChart Android Examples Suite, which ships as part of our Android Charts SDK.
The Vital Signs demo showcases how to use SciChart Android Charts in a medical context.
There are four channels of data simulated, showing how real-time, high performance ECG/EKG charts & graphs can be drawn with SciChart Android to monitor blood pressure, SPO2 blood oxygen, and volumetric flow enabling you to create medical apps using an Android phone or Embedded systems.
SciChart helps you shortcut development of medical applications by providing rich, real time, high performance & reliable Android charts for use in Vital-Signs monitors, blood pressure monitors, Electro Cardiogram, medical Ventilators, patient monitors, digital stethoscopes and more.
If you are creating an app that needs to visualize body temperature, pulse rate, respiration rate, blood pressure, or similar, choose SciChart to shortcut your development time & get to market faster with our well-tested, reliable Android Chart library.
The full source code for the Android Vital Signs ECG/EKG Medical Demo example is included below (Scroll down!).
Did you know you can also view the source code from one of the following sources as well?
<?xml version="1.0" encoding="utf-8"?>
<!--*************************************************************************-->
<!-- SCICHART® Copyright SciChart Ltd. 2011-2020. All rights reserved. -->
<!-- -->
<!-- Web: http://www.scichart.com -->
<!-- Support: support@scichart.com -->
<!-- Sales: sales@scichart.com -->
<!-- -->
<!-- example_vital_signs_heart_rate_indicator_layout.xml is part of the SCICHART® Examples. Permission is hereby granted -->
<!-- to modify, create derivative works, distribute and publish any part of this source -->
<!-- code whether for commercial, private or personal use. -->
<!-- -->
<!-- The SCICHART® examples are distributed in the hope that they will be useful, but -->
<!-- without any warranty. It is provided "AS IS" without warranty of any kind, either -->
<!-- expressed or implied. -->
<!--*************************************************************************-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/example_ecg_layout_background">
<ImageView
android:id="@+id/heartIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginEnd="8dp"
android:contentDescription="@string/heart_icon_description"
android:src="@drawable/example_heart_icon_24dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/bpmValueLabel"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:gravity="end|bottom"
android:singleLine="true"
android:textColor="@color/heart_rate_color"
android:textStyle="normal|bold|italic"
app:autoSizeTextType="uniform"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/heartIcon"
tools:text="69"
tools:textColor="@color/color_primary" />
<TextView
android:id="@+id/ecgLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:singleLine="true"
android:text="@string/ecg"
android:textAlignment="viewStart"
android:textColor="@color/heart_rate_color"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/heartRateInfoLabel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginBottom="8dp"
android:text="@string/heartRateInfo"
android:gravity="bottom|start"
android:textColor="@color/heart_rate_color"
app:autoSizeTextType="uniform"
app:autoSizeMaxTextSize="10sp"
app:autoSizeMinTextSize="6sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<!--*************************************************************************-->
<!-- SCICHART® Copyright SciChart Ltd. 2011-2020. All rights reserved. -->
<!-- -->
<!-- Web: http://www.scichart.com -->
<!-- Support: support@scichart.com -->
<!-- Sales: sales@scichart.com -->
<!-- -->
<!-- example_vital_signs_monitor_fragment.xml is part of the SCICHART® Examples. Permission is hereby granted -->
<!-- to modify, create derivative works, distribute and publish any part of this source -->
<!-- code whether for commercial, private or personal use. -->
<!-- -->
<!-- The SCICHART® examples are distributed in the hope that they will be useful, but -->
<!-- without any warranty. It is provided "AS IS" without warranty of any kind, either -->
<!-- expressed or implied. -->
<!--*************************************************************************-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout_editor_absoluteX="12dp"
tools:layout_editor_absoluteY="12dp">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/topIndicatorsGuideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.15" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/bottomIndicatorsGuideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.85" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/verticalGuideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
<include
android:id="@+id/heartRateIndicator"
layout="@layout/example_vital_signs_heart_rate_indicator_layout"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/topIndicatorsGuideline"
app:layout_constraintEnd_toStartOf="@id/verticalGuideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<include
android:id="@+id/bloodPressureIndicator"
layout="@layout/example_vital_signs_blood_pressure_indicator_layout"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/topIndicatorsGuideline"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/verticalGuideline"
app:layout_constraintTop_toTopOf="parent" />
<include
android:id="@+id/bloodVolumeIndicator"
layout="@layout/example_vital_signs_blood_volume_indicator_layout"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/verticalGuideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/bottomIndicatorsGuideline" />
<include
android:id="@+id/bloodOxygenationIndicator"
layout="@layout/example_vital_signs_blood_oxygenation_indicator_layout"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/verticalGuideline"
app:layout_constraintTop_toBottomOf="@id/bottomIndicatorsGuideline" />
<com.scichart.charting.visuals.SciChartSurface
android:id="@+id/chart"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/bottomIndicatorsGuideline"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/topIndicatorsGuideline" />
</androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<!--*************************************************************************-->
<!-- SCICHART® Copyright SciChart Ltd. 2011-2020. All rights reserved. -->
<!-- -->
<!-- Web: http://www.scichart.com -->
<!-- Support: support@scichart.com -->
<!-- Sales: sales@scichart.com -->
<!-- -->
<!-- example_vital_signs_blood_volume_indicator_layout.xml is part of the SCICHART® Examples. Permission is hereby granted -->
<!-- to modify, create derivative works, distribute and publish any part of this source -->
<!-- code whether for commercial, private or personal use. -->
<!-- -->
<!-- The SCICHART® examples are distributed in the hope that they will be useful, but -->
<!-- without any warranty. It is provided "AS IS" without warranty of any kind, either -->
<!-- expressed or implied. -->
<!--*************************************************************************-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/example_ecg_layout_background">
<TextView
android:id="@+id/svLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:text="@string/sv"
android:textColor="@color/blood_volume_color"
android:textSize="18sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/svInfoLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginEnd="8dp"
android:text="@string/svInfo"
android:textColor="@color/blood_volume_color"
android:textSize="8sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/bloodVolumeValueLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:gravity="bottom|end"
android:textAlignment="gravity"
android:textColor="@color/blood_volume_color"
android:textSize="36sp"
android:textStyle="bold|italic"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintLeft_toRightOf="@+id/svBar2"
app:layout_constraintRight_toRightOf="parent"
tools:text="13.0" />
<com.scichart.examples.components.StepProgressBar
android:id="@+id/svBar1"
android:layout_width="15dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginBottom="4dp"
app:barSize="3dp"
app:isVertical="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:max="10"
app:progressBackgroundColor="@android:color/transparent"
app:progressColor="@color/blood_volume_color"
app:spacing="2dp"
tools:progress="9" />
<com.scichart.examples.components.StepProgressBar
android:id="@+id/svBar2"
android:layout_width="15dp"
android:layout_height="wrap_content"
android:layout_marginStart="2dp"
android:layout_marginBottom="4dp"
app:barSize="3dp"
app:isVertical="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@+id/svBar1"
app:max="10"
app:progressBackgroundColor="@android:color/transparent"
app:progressColor="@color/blood_volume_color"
app:spacing="2dp"
tools:progress="7" />
</androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<!--*************************************************************************-->
<!-- SCICHART® Copyright SciChart Ltd. 2011-2020. All rights reserved. -->
<!-- -->
<!-- Web: http://www.scichart.com -->
<!-- Support: support@scichart.com -->
<!-- Sales: sales@scichart.com -->
<!-- -->
<!-- example_vital_signs_blood_pressure_indicator_layout.xml is part of the SCICHART® Examples. Permission is hereby granted -->
<!-- to modify, create derivative works, distribute and publish any part of this source -->
<!-- code whether for commercial, private or personal use. -->
<!-- -->
<!-- The SCICHART® examples are distributed in the hope that they will be useful, but -->
<!-- without any warranty. It is provided "AS IS" without warranty of any kind, either -->
<!-- expressed or implied. -->
<!--*************************************************************************-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/example_ecg_layout_background">
<TextView
android:id="@+id/nibpLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:text="@string/nibp"
android:textColor="@color/blood_pressure_color"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/nibpInfoLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginEnd="8dp"
android:text="@string/nibpInfo"
android:textAlignment="textEnd"
android:textColor="@color/blood_pressure_color"
android:textSize="8sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.scichart.examples.components.StepProgressBar
android:id="@+id/bloodPressureBar"
android:layout_width="wrap_content"
android:layout_height="16dp"
android:layout_marginStart="8dp"
android:layout_marginBottom="8dp"
app:spacing="2dp"
app:barSize="5dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:max="5"
app:progressBackgroundColor="@color/blood_pressure_bar_background_color"
app:progressColor="@color/blood_pressure_bar_color"
tools:progress="5" />
<TextView
android:id="@+id/bloodPressureValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:gravity="bottom|end"
android:textColor="@color/blood_pressure_color"
android:textSize="36sp"
android:textStyle="bold|italic"
app:autoSizeTextType="uniform"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toEndOf="@+id/bloodPressureBar"
tools:text="115/70" />
</androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<!--*************************************************************************-->
<!-- SCICHART® Copyright SciChart Ltd. 2011-2020. All rights reserved. -->
<!-- -->
<!-- Web: http://www.scichart.com -->
<!-- Support: support@scichart.com -->
<!-- Sales: sales@scichart.com -->
<!-- -->
<!-- example_vital_signs_blood_oxygenation_indicator_layout.xml is part of the SCICHART® Examples. Permission is hereby granted -->
<!-- to modify, create derivative works, distribute and publish any part of this source -->
<!-- code whether for commercial, private or personal use. -->
<!-- -->
<!-- The SCICHART® examples are distributed in the hope that they will be useful, but -->
<!-- without any warranty. It is provided "AS IS" without warranty of any kind, either -->
<!-- expressed or implied. -->
<!--*************************************************************************-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/example_ecg_layout_background">
<TextView
android:id="@+id/spoLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginTop="4dp"
android:gravity="start|top"
android:paddingBottom="5dp"
android:text="@string/spo2"
android:textAlignment="center"
android:textColor="@color/blood_oxygenation_color"
android:textSize="18sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/spoClockLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginRight="8dp"
android:gravity="top|end"
android:textAlignment="viewStart"
android:textColor="@color/blood_oxygenation_color"
android:textSize="14sp"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="16:40" />
<TextView
android:id="@+id/spoInfoLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginBottom="4dp"
android:text="@string/spoInfo"
android:textColor="@color/blood_oxygenation_color"
android:textSize="10sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent" />
<TextView
android:id="@+id/spoValueLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:textAlignment="textEnd"
android:textColor="@color/blood_oxygenation_color"
android:textSize="36sp"
android:textStyle="normal|bold|italic"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
tools:text="98" />
</androidx.constraintlayout.widget.ConstraintLayout>
//******************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2020. All rights reserved.
//
// Web: http://www.scichart.com
// Support: support@scichart.com
// Sales: sales@scichart.com
//
// VitalSignsIndicatorsProvider.java is part of the SCICHART® Examples. Permission is hereby granted
// to modify, create derivative works, distribute and publish any part of this source
// code whether for commercial, private or personal use.
//
// The SCICHART® examples are distributed in the hope that they will be useful, but
// without any warranty. It is provided "AS IS" without warranty of any kind, either
// expressed or implied.
//******************************************************************************
package com.scichart.examples.fragments.showcase.vitalSignsMonitor;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;
import java.util.Random;
public class VitalSignsIndicatorsProvider {
private final Random random = new Random();
private final SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm", Locale.getDefault());
private static final String[] BPM_VALUES = new String[] {"67", "69", "72", "74"};
private static final String[] BP_VALUES = new String[] {"120/70", "115/70", "115/75", "120/75"};
private static final int[] BPB_VALUES = new int[] {3, 4, 5};
private static final String[] BV_VALUES = new String[] {"13.1", "13.2", "13.3", "13.0"};
private static final int[] BVB_VALUES = new int[] {8, 9, 10};
private static final String[] BO_VALUES = new String[] { "93", "95", "96", "97"};
private String bpmValue = BPM_VALUES[0];
private String bpValue= BP_VALUES[0];
private int bpbValue = BPB_VALUES[0];
private String bvValue = BV_VALUES[0];
private int bvBar1Value = BVB_VALUES[0];
private int bvBar2Value = BVB_VALUES[0];
private String spoValue = BO_VALUES[0];
private String spoClockValue = getTimeString();
public String getBpmValue() {
return bpmValue;
}
public String getBpValue() {
return bpValue;
}
public int getBpbValue() {
return bpbValue;
}
public String getBvValue() {
return bvValue;
}
public int getBvBar1Value() {
return bvBar1Value;
}
public int getBvBar2Value() {
return bvBar2Value;
}
public String getSpoValue() {
return spoValue;
}
public String getSpoClockValue() {
return spoClockValue;
}
public void update() {
bpmValue = randomString(BPM_VALUES);
bpValue = randomString(BP_VALUES);
bpbValue = randomInt(BPB_VALUES);
bvValue = randomString(BV_VALUES);
bvBar1Value = randomInt(BVB_VALUES);
bvBar2Value = randomInt(BVB_VALUES);
spoValue = randomString(BO_VALUES);
spoClockValue = getTimeString();
}
private String randomString(String[] values){
return values[random.nextInt(values.length)];
}
private int randomInt(int[] values){
return values[random.nextInt(values.length)];
}
private String getTimeString() {
return timeFormat.format(Calendar.getInstance().getTime());
}
}
//******************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2019. All rights reserved.
//
// Web: http://www.scichart.com
// Support: support@scichart.com
// Sales: sales@scichart.com
//
// VitalSignsData.java is part of the SCICHART® Examples. Permission is hereby granted
// to modify, create derivative works, distribute and publish any part of this source
// code whether for commercial, private or personal use.
//
// The SCICHART® examples are distributed in the hope that they will be useful, but
// without any warranty. It is provided "AS IS" without warranty of any kind, either
// expressed or implied.
//******************************************************************************
package com.scichart.examples.fragments.showcase.vitalSignsMonitor;
public class VitalSignsData {
public final double xValue;
public final double ecgHeartRate;
public final double bloodPressure;
public final double bloodVolume;
public final double bloodOxygenation;
public final boolean isATrace;
public VitalSignsData(double xValue, double ecgHeartRate, double bloodPressure, double bloodVolume, double bloodOxygenation, boolean isATrace) {
this.xValue = xValue;
this.ecgHeartRate = ecgHeartRate;
this.bloodPressure = bloodPressure;
this.bloodVolume = bloodVolume;
this.bloodOxygenation = bloodOxygenation;
this.isATrace = isATrace;
}
}
//******************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2019. All rights reserved.
//
// Web: http://www.scichart.com
// Support: support@scichart.com
// Sales: sales@scichart.com
//
// DefaultVitalSignsDataProvider.java is part of the SCICHART® Examples. Permission is hereby granted
// to modify, create derivative works, distribute and publish any part of this source
// code whether for commercial, private or personal use.
//
// The SCICHART® examples are distributed in the hope that they will be useful, but
// without any warranty. It is provided "AS IS" without warranty of any kind, either
// expressed or implied.
//******************************************************************************
package com.scichart.examples.fragments.showcase.vitalSignsMonitor;
import android.content.Context;
import android.util.Log;
import com.scichart.core.model.DoubleValues;
import com.scichart.core.utility.ListUtil;
import com.scichart.data.model.DoubleRange;
import com.scichart.data.model.SciListUtil;
import com.scichart.examples.fragments.base.DataProviderBase;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.concurrent.TimeUnit;
import static java.lang.Double.parseDouble;
public class DefaultVitalSignsDataProvider extends DataProviderBase<VitalSignsData> {
//1. Heart rate or pulse rate (ECG HR)
//2. Blood Pressure (NI BP)
//3. Blood Volume (SV ml)
//4. Blood Oxygenation (SPo2)
private static final String ECG_TRACES = "data/EcgTraces.csv";
private static final float SAMPLE_RATE = 800f;
private int currentIndex = 0;
private int totalIndex = 0;
private boolean isATrace = false;
private final DoubleValues xValues = new DoubleValues();
private final DoubleValues ecgHeartRate= new DoubleValues();
private final DoubleValues bloodPressure = new DoubleValues();
private final DoubleValues bloodVolume = new DoubleValues();
private final DoubleValues bloodOxygenation = new DoubleValues();
public DefaultVitalSignsDataProvider(Context context) {
super(1000L, TimeUnit.MICROSECONDS);
try {
final BufferedReader reader = new BufferedReader(new InputStreamReader(context.getAssets().open(ECG_TRACES)));
String line = reader.readLine();
while(line != null) {
final String[] split = line.split(",");
xValues.add(parseDouble(split[0]));
ecgHeartRate.add(parseDouble(split[1]));
bloodPressure.add(parseDouble(split[2]));
bloodVolume.add(parseDouble(split[3]));
bloodOxygenation.add(parseDouble(split[4]));
line = reader.readLine();
}
} catch (Exception e){
Log.e("Load ECG", "Failed to load ECG", e);
}
}
@Override
protected VitalSignsData onNext() {
if(currentIndex >= xValues.size()) {
currentIndex = 0;
}
final float time = totalIndex / SAMPLE_RATE % 10;
final double ecgHeartRate = this.ecgHeartRate.get(currentIndex);
final double bloodPressure = this.bloodPressure.get(currentIndex);
final double bloodVolume = this.bloodVolume.get(currentIndex);
final double bloodOxygenation = this.bloodOxygenation.get(currentIndex);
final VitalSignsData data = new VitalSignsData(time, ecgHeartRate, bloodPressure, bloodVolume, bloodOxygenation, isATrace);
currentIndex++;
totalIndex++;
if(totalIndex % 8000 == 0) {
isATrace = !isATrace;
}
return data;
}
public final DoubleRange getEcgHeartRateRange() {
return getMinMaxRange(ecgHeartRate);
}
public final DoubleRange getBloodPressureRange() {
return getMinMaxRange(bloodPressure);
}
public final DoubleRange getBloodVolumeRange() {
return getMinMaxRange(bloodVolume);
}
public final DoubleRange getBloodOxygenationRange() {
return getMinMaxRange(bloodOxygenation);
}
private static DoubleRange getMinMaxRange(DoubleValues values) {
final DoubleRange range = new DoubleRange();
SciListUtil.instance().minMax(values.getItemsArray(), 0, values.size(), range);
range.growBy(0.1, 0.1);
return range;
}
}
//******************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2019. All rights reserved.
//
// Web: http://www.scichart.com
// Support: support@scichart.com
// Sales: sales@scichart.com
//
// VitalSignsMonitorShowcaseFragment.java is part of the SCICHART® Examples. Permission is hereby granted
// to modify, create derivative works, distribute and publish any part of this source
// code whether for commercial, private or personal use.
//
// The SCICHART® examples are distributed in the hope that they will be useful, but
// without any warranty. It is provided "AS IS" without warranty of any kind, either
// expressed or implied.
//******************************************************************************
package com.scichart.examples.fragments.showcase.vitalSignsMonitor;
import android.content.Context;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.core.content.ContextCompat;
import com.scichart.charting.layoutManagers.ChartLayoutState;
import com.scichart.charting.layoutManagers.DefaultLayoutManager;
import com.scichart.charting.layoutManagers.VerticalAxisLayoutStrategy;
import com.scichart.charting.model.dataSeries.IDataSeries;
import com.scichart.charting.model.dataSeries.XyDataSeries;
import com.scichart.charting.visuals.SciChartSurface;
import com.scichart.charting.visuals.axes.AutoRange;
import com.scichart.charting.visuals.axes.AxisLayoutState;
import com.scichart.charting.visuals.axes.IAxis;
import com.scichart.charting.visuals.axes.NumericAxis;
import com.scichart.charting.visuals.pointmarkers.EllipsePointMarker;
import com.scichart.charting.visuals.renderableSeries.IRenderableSeries;
import com.scichart.charting.visuals.renderableSeries.XyRenderableSeriesBase;
import com.scichart.charting.visuals.renderableSeries.paletteProviders.IStrokePaletteProvider;
import com.scichart.charting.visuals.renderableSeries.paletteProviders.PaletteProviderBase;
import com.scichart.core.framework.UpdateSuspender;
import com.scichart.core.model.DoubleValues;
import com.scichart.core.model.IntegerValues;
import com.scichart.data.model.DoubleRange;
import com.scichart.drawing.common.PenStyle;
import com.scichart.drawing.utility.ColorUtil;
import com.scichart.examples.R;
import com.scichart.examples.components.StepProgressBar;
import com.scichart.examples.fragments.base.ShowcaseExampleBaseFragment;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import butterknife.BindView;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
public class VitalSignsMonitorShowcaseFragment extends ShowcaseExampleBaseFragment {
private static final int FIFO_CAPACITY = 7850;
private final XyDataSeries<Double, Double> ecgDataSeries = newDataSeries(FIFO_CAPACITY);
private final XyDataSeries<Double, Double> ecgSweepDataSeries = newDataSeries(FIFO_CAPACITY);
private final XyDataSeries<Double, Double> bloodPressureDataSeries = newDataSeries(FIFO_CAPACITY);
private final XyDataSeries<Double, Double> bloodPressureSweepDataSeries = newDataSeries(FIFO_CAPACITY);
private final XyDataSeries<Double, Double> bloodVolumeDataSeries = newDataSeries(FIFO_CAPACITY);
private final XyDataSeries<Double, Double> bloodVolumeSweepDataSeries = newDataSeries(FIFO_CAPACITY);
private final XyDataSeries<Double, Double> bloodOxygenationDataSeries = newDataSeries(FIFO_CAPACITY);
private final XyDataSeries<Double, Double> bloodOxygenationSweepDataSeries = newDataSeries(FIFO_CAPACITY);
private final XyDataSeries<Double, Double> lastEcgSweepDataSeries = newDataSeries(1);
private final XyDataSeries<Double, Double> lastBloodPressureDataSeries = newDataSeries(1);
private final XyDataSeries<Double, Double> lastBloodVolumeDataSeries = newDataSeries(1);
private final XyDataSeries<Double, Double> lastBloodOxygenationSweepDataSeries = newDataSeries(1);
private final VitalSignsIndicatorsProvider indicatorsProvider = new VitalSignsIndicatorsProvider();
@BindView(R.id.chart)
SciChartSurface chart;
@BindView(R.id.heartIcon)
ImageView heartIcon;
@BindView(R.id.bpmValueLabel)
TextView bpmValue;
@BindView(R.id.bloodPressureValue)
TextView bpValue;
@BindView(R.id.bloodPressureBar)
StepProgressBar bpBar;
@BindView(R.id.spoValueLabel)
TextView spoValue;
@BindView(R.id.spoClockLabel)
TextView spoClockValue;
@BindView(R.id.bloodVolumeValueLabel)
TextView bvValue;
@BindView(R.id.svBar1)
StepProgressBar svBar1;
@BindView(R.id.svBar2)
StepProgressBar svBar2;
private final EcgDataBatch dataBatch = new EcgDataBatch();
@Override
protected int getLayoutId() {
return R.layout.example_vital_signs_monitor_fragment;
}
@Override
protected void initExample() {
final DefaultVitalSignsDataProvider dataProvider = new DefaultVitalSignsDataProvider(getActivity());
setUpChart(dataProvider);
dataProvider.getData().buffer(50, TimeUnit.MILLISECONDS).doOnNext(ecgData -> {
if(ecgData.isEmpty()) return;
dataBatch.updateData(ecgData);
UpdateSuspender.using(chart, () -> {
final DoubleValues xValues = dataBatch.xValues;
ecgDataSeries.append(xValues, dataBatch.ecgHeartRateValuesA);
ecgSweepDataSeries.append(xValues, dataBatch.ecgHeartRateValuesB);
bloodPressureDataSeries.append(xValues, dataBatch.bloodPressureValuesA);
bloodPressureSweepDataSeries.append(xValues, dataBatch.bloodPressureValuesB);
bloodOxygenationDataSeries.append(xValues, dataBatch.bloodOxygenationA);
bloodOxygenationSweepDataSeries.append(xValues, dataBatch.bloodOxygenationB);
bloodVolumeDataSeries.append(xValues, dataBatch.bloodVolumeValuesA);
bloodVolumeSweepDataSeries.append(xValues, dataBatch.bloodVolumeValuesB);
final VitalSignsData lastVitalSignsData = dataBatch.lastVitalSignsData;
final double xValue = lastVitalSignsData.xValue;
lastEcgSweepDataSeries.append(xValue, lastVitalSignsData.ecgHeartRate);
lastBloodPressureDataSeries.append(xValue, lastVitalSignsData.bloodPressure);
lastBloodOxygenationSweepDataSeries.append(xValue, lastVitalSignsData.bloodOxygenation);
lastBloodVolumeDataSeries.append(xValue, lastVitalSignsData.bloodVolume);
});
}).compose(bindToLifecycle()).subscribe();
updateIndicators(0);
Observable.interval(0, 1, TimeUnit.SECONDS, AndroidSchedulers.mainThread())
.doOnNext(this::updateIndicators)
.compose(bindToLifecycle()).subscribe();
}
private void updateIndicators(long time) {
heartIcon.setVisibility(time % 2 == 0 ? View.VISIBLE : View.INVISIBLE);
if(time % 5 == 0) {
indicatorsProvider.update();
bpmValue.setText(indicatorsProvider.getBpmValue());
bpValue.setText(indicatorsProvider.getBpValue());
bpBar.setProgress(indicatorsProvider.getBpbValue());
bvValue.setText(indicatorsProvider.getBvValue());
svBar1.setProgress(indicatorsProvider.getBvBar1Value());
svBar2.setProgress(indicatorsProvider.getBvBar2Value());
spoValue.setText(indicatorsProvider.getSpoValue());
spoClockValue.setText(indicatorsProvider.getSpoClockValue());
}
}
private void setUpChart(DefaultVitalSignsDataProvider dataProvider) {
final NumericAxis xAxis = sciChartBuilder.newNumericAxis()
.withVisibleRange(0, 10)
.withAutoRangeMode(AutoRange.Never)
.withDrawMinorGridLines(false)
.withDrawMajorBands(false)
.withVisibility(View.GONE)
.build();
final String ecgId = "ecgId";
final String bloodPressureId = "bloodPressureId";
final String bloodVolumeId = "bloodVolumeId";
final String bloodOxygenationId = "bloodOxygenationId";
final NumericAxis yAxisEcg = generateYAxis(ecgId, dataProvider.getEcgHeartRateRange());
final NumericAxis yAxisPressure = generateYAxis(bloodPressureId, dataProvider.getBloodPressureRange());
final NumericAxis yAxisVolume = generateYAxis(bloodVolumeId, dataProvider.getBloodVolumeRange());
final NumericAxis yAxisOxygenation = generateYAxis(bloodOxygenationId, dataProvider.getBloodOxygenationRange());
final Context context = getActivity();
final int heartRateColor = ContextCompat.getColor(context, R.color.heart_rate_color);
final int bloodPressureColor = ContextCompat.getColor(context, R.color.blood_pressure_color);
final int bloodVolumeColor = ContextCompat.getColor(context, R.color.blood_volume_color);
final int bloodOxygenation = ContextCompat.getColor(context, R.color.blood_oxygenation_color);
UpdateSuspender.using(chart, () -> {
Collections.addAll(chart.getXAxes(), xAxis);
Collections.addAll(chart.getYAxes(), yAxisEcg, yAxisPressure, yAxisVolume, yAxisOxygenation);
Collections.addAll(chart.getRenderableSeries(),
generateLineSeries(ecgId, ecgDataSeries, sciChartBuilder.newPen().withColor(heartRateColor).withThickness(1f).build()),
generateLineSeries(ecgId, ecgSweepDataSeries, sciChartBuilder.newPen().withColor(heartRateColor).withThickness(1f).build()),
generateScatterForLastAppendedPoint(ecgId, lastEcgSweepDataSeries),
generateLineSeries(bloodPressureId, bloodPressureDataSeries, sciChartBuilder.newPen().withColor(bloodPressureColor).withThickness(1f).build()),
generateLineSeries(bloodPressureId, bloodPressureSweepDataSeries, sciChartBuilder.newPen().withColor(bloodPressureColor).withThickness(1f).build()),
generateScatterForLastAppendedPoint(bloodPressureId, lastBloodPressureDataSeries),
generateLineSeries(bloodVolumeId, bloodVolumeDataSeries, sciChartBuilder.newPen().withColor(bloodVolumeColor).withThickness(1f).build()),
generateLineSeries(bloodVolumeId, bloodVolumeSweepDataSeries, sciChartBuilder.newPen().withColor(bloodVolumeColor).withThickness(1f).build()),
generateScatterForLastAppendedPoint(bloodVolumeId, lastBloodVolumeDataSeries),
generateLineSeries(bloodOxygenationId, bloodOxygenationDataSeries, sciChartBuilder.newPen().withColor(bloodOxygenation).withThickness(1f).build()),
generateLineSeries(bloodOxygenationId, bloodOxygenationSweepDataSeries, sciChartBuilder.newPen().withColor(bloodOxygenation).withThickness(1f).build()),
generateScatterForLastAppendedPoint(bloodOxygenationId, lastBloodOxygenationSweepDataSeries)
);
chart.setLayoutManager(new DefaultLayoutManager.Builder().setRightOuterAxesLayoutStrategy(new RightAlignedOuterVerticallyStackedYAxisLayoutStrategy()).build());
});
}
private NumericAxis generateYAxis(String id, DoubleRange visibleRange) {
return sciChartBuilder.newNumericAxis().withAxisId(id).withVisibility(View.GONE).withVisibleRange(visibleRange).withAutoRangeMode(AutoRange.Never).withDrawMajorBands(false).withDrawMinorGridLines(false).withDrawMajorGridLines(false).build();
}
private IRenderableSeries generateLineSeries(String yAxisId, IDataSeries ds, PenStyle strokeStyle) {
return sciChartBuilder.newLineSeries().withDataSeries(ds).withYAxisId(yAxisId).withStrokeStyle(strokeStyle).withPaletteProvider(new DimTracePaletteProvider()).build();
}
private IRenderableSeries generateScatterForLastAppendedPoint(String yAxisId, IDataSeries ds) {
final EllipsePointMarker pm = sciChartBuilder.newPointMarker(new EllipsePointMarker())
.withSize(4)
.withFill(ColorUtil.White)
.withStroke(ColorUtil.White, 1f)
.build();
return sciChartBuilder.newScatterSeries()
.withDataSeries(ds)
.withYAxisId(yAxisId)
.withPointMarker(pm)
.build();
}
private static XyDataSeries<Double, Double> newDataSeries(int fifoCapacity) {
final XyDataSeries<Double, Double> ds = new XyDataSeries<>(Double.class, Double.class);
ds.setFifoCapacity(fifoCapacity);
return ds;
}
private static class EcgDataBatch {
private final DoubleValues xValues = new DoubleValues();
private final DoubleValues ecgHeartRateValuesA = new DoubleValues();
private final DoubleValues bloodPressureValuesA = new DoubleValues();
private final DoubleValues bloodVolumeValuesA = new DoubleValues();
private final DoubleValues bloodOxygenationA = new DoubleValues();
private final DoubleValues ecgHeartRateValuesB = new DoubleValues();
private final DoubleValues bloodPressureValuesB = new DoubleValues();
private final DoubleValues bloodVolumeValuesB = new DoubleValues();
private final DoubleValues bloodOxygenationB = new DoubleValues();
VitalSignsData lastVitalSignsData;
final void updateData(List<VitalSignsData> vitalSignsDataList) {
xValues.clear();
ecgHeartRateValuesA.clear();
ecgHeartRateValuesB.clear();
bloodPressureValuesA.clear();
bloodPressureValuesB.clear();
bloodVolumeValuesA.clear();
bloodVolumeValuesB.clear();
bloodOxygenationA.clear();
bloodOxygenationB.clear();
final int size = vitalSignsDataList.size();
for (int i = 0; i < size; i++) {
final VitalSignsData vitalSignsData = vitalSignsDataList.get(i);
xValues.add(vitalSignsData.xValue);
if (vitalSignsData.isATrace) {
ecgHeartRateValuesA.add(vitalSignsData.ecgHeartRate);
bloodPressureValuesA.add(vitalSignsData.bloodPressure);
bloodVolumeValuesA.add(vitalSignsData.bloodVolume);
bloodOxygenationA.add(vitalSignsData.bloodOxygenation);
ecgHeartRateValuesB.add(Double.NaN);
bloodPressureValuesB.add(Double.NaN);
bloodVolumeValuesB.add(Double.NaN);
bloodOxygenationB.add(Double.NaN);
} else {
ecgHeartRateValuesB.add(vitalSignsData.ecgHeartRate);
bloodPressureValuesB.add(vitalSignsData.bloodPressure);
bloodVolumeValuesB.add(vitalSignsData.bloodVolume);
bloodOxygenationB.add(vitalSignsData.bloodOxygenation);
ecgHeartRateValuesA.add(Double.NaN);
bloodPressureValuesA.add(Double.NaN);
bloodVolumeValuesA.add(Double.NaN);
bloodOxygenationA.add(Double.NaN);
}
}
lastVitalSignsData = vitalSignsDataList.get(size - 1);
}
}
private static class RightAlignedOuterVerticallyStackedYAxisLayoutStrategy extends VerticalAxisLayoutStrategy {
@Override
public void measureAxes(int availableWidth, int availableHeight, ChartLayoutState chartLayoutState) {
for (int i = 0, size = axes.size(); i < size; i++) {
final IAxis axis = axes.get(i);
axis.updateAxisMeasurements();
final AxisLayoutState axisLayoutState = axis.getAxisLayoutState();
chartLayoutState.rightOuterAreaSize = Math.max(getRequiredAxisSize(axisLayoutState), chartLayoutState.rightOuterAreaSize);
}
}
@Override
public void layoutAxes(int left, int top, int right, int bottom) {
final int size = axes.size();
final int height = bottom - top;
final int axisHeight = height / size;
int topPlacement = top;
for (int i = 0; i < size; i++) {
final IAxis axis = axes.get(i);
final AxisLayoutState axisLayoutState = axis.getAxisLayoutState();
final int bottomPlacement = Math.round(topPlacement + axisHeight);
axis.layoutArea(left, topPlacement, left + getRequiredAxisSize(axisLayoutState), bottomPlacement);
topPlacement = bottomPlacement;
}
}
}
private static class DimTracePaletteProvider extends PaletteProviderBase<XyRenderableSeriesBase> implements IStrokePaletteProvider {
private final IntegerValues colors = new IntegerValues();
private final double startOpacity;
private final double diffOpacity;
public DimTracePaletteProvider() {
super(XyRenderableSeriesBase.class);
this.startOpacity = 0.2;
this.diffOpacity = 1 - startOpacity;
}
@Override
public IntegerValues getStrokeColors() {
return colors;
}
@Override
public void update() {
final int defaultColor = renderableSeries.getStrokeStyle().getColor();
final int size = renderableSeries.getCurrentRenderPassData().pointsCount();
colors.setSize(size);
final int[] colorsArray = colors.getItemsArray();
final double doubleSize = (double)size;
for (int i = 0; i < size; i++) {
final double faction = i / doubleSize;
final float opacity = (float) (startOpacity + faction * diffOpacity);
colorsArray[i] = ColorUtil.argb(defaultColor, opacity);
}
}
}
}