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?
//******************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2021. All rights reserved.
//
// Web: http://www.scichart.com
// Support: support@scichart.com
// Sales: sales@scichart.com
//
// VitalSignsMonitorShowcaseFragment.kt is part of SCICHART®, High Performance Scientific Charts
// For full terms and conditions of the license, see http://www.scichart.com/scichart-eula/
//
// This source code is protected by international copyright law. Unauthorized
// reproduction, reverse-engineering, or distribution of all or any portion of
// this source code is strictly prohibited.
//
// This source code contains confidential and proprietary trade secrets of
// SciChart Ltd., and should at no time be copied, transferred, sold,
// distributed or made available without express written permission.
//******************************************************************************
package com.scichart.examples.fragments.featuredApps.medicalCharts.kt.vitalSignsMonitor
import android.view.LayoutInflater
import android.view.View
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.axes.AutoRange.Never
import com.scichart.charting.visuals.axes.NumericAxis
import com.scichart.charting.visuals.renderableSeries.FastLineRenderableSeries
import com.scichart.charting.visuals.renderableSeries.IRenderableSeries
import com.scichart.charting.visuals.renderableSeries.XyRenderableSeriesBase
import com.scichart.charting.visuals.renderableSeries.XyScatterRenderableSeries
import com.scichart.charting.visuals.renderableSeries.paletteProviders.IStrokePaletteProvider
import com.scichart.charting.visuals.renderableSeries.paletteProviders.PaletteProviderBase
import com.scichart.core.model.IntegerValues
import com.scichart.data.model.DoubleRange
import com.scichart.drawing.utility.ColorUtil
import com.scichart.examples.R
import com.scichart.examples.databinding.ExampleVitalSignsMonitorFragmentBinding
import com.scichart.examples.fragments.base.ShowcaseExampleBaseFragment
import com.scichart.examples.fragments.featuredApps.medicalCharts.vitalSignsMonitor.DefaultVitalSignsDataProvider
import com.scichart.examples.fragments.featuredApps.medicalCharts.vitalSignsMonitor.EcgDataBatch
import com.scichart.examples.fragments.featuredApps.medicalCharts.vitalSignsMonitor.VitalSignsData
import com.scichart.examples.fragments.featuredApps.medicalCharts.vitalSignsMonitor.VitalSignsIndicatorsProvider
import com.scichart.examples.utils.scichartExtensions.*
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.math.max
import kotlin.math.roundToInt
class VitalSignsMonitorShowcaseFragment : ShowcaseExampleBaseFragment<ExampleVitalSignsMonitorFragmentBinding>() {
private val ecgDataSeries = newDataSeries(FIFO_CAPACITY)
private val ecgSweepDataSeries = newDataSeries(FIFO_CAPACITY)
private val bloodPressureDataSeries = newDataSeries(FIFO_CAPACITY)
private val bloodPressureSweepDataSeries = newDataSeries(FIFO_CAPACITY)
private val bloodVolumeDataSeries = newDataSeries(FIFO_CAPACITY)
private val bloodVolumeSweepDataSeries = newDataSeries(FIFO_CAPACITY)
private val bloodOxygenationDataSeries = newDataSeries(FIFO_CAPACITY)
private val bloodOxygenationSweepDataSeries = newDataSeries(FIFO_CAPACITY)
private val lastEcgSweepDataSeries = newDataSeries(1)
private val lastBloodPressureDataSeries = newDataSeries(1)
private val lastBloodVolumeDataSeries = newDataSeries(1)
private val lastBloodOxygenationSweepDataSeries = newDataSeries(1)
private val indicatorsProvider = VitalSignsIndicatorsProvider()
private val dataBatch = EcgDataBatch()
override fun inflateBinding(inflater: LayoutInflater): ExampleVitalSignsMonitorFragmentBinding {
return ExampleVitalSignsMonitorFragmentBinding.inflate(inflater)
}
override fun initExample(binding: ExampleVitalSignsMonitorFragmentBinding) {
val dataProvider = DefaultVitalSignsDataProvider(requireContext())
setUpChart(dataProvider)
dataProvider.data.buffer(50, TimeUnit.MILLISECONDS).doOnNext { ecgData: List<VitalSignsData> ->
if (ecgData.isEmpty()) return@doOnNext
dataBatch.updateData(ecgData)
binding.surface.suspendUpdates {
val 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)
val lastVitalSignsData = dataBatch.lastVitalSignsData
val 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(::updateIndicators)
.compose(bindToLifecycle()).subscribe()
}
private fun setUpChart(dataProvider: DefaultVitalSignsDataProvider) {
val context = requireContext()
val heartRateColor = ContextCompat.getColor(context, R.color.heart_rate_color)
val bloodPressureColor = ContextCompat.getColor(context, R.color.blood_pressure_color)
val bloodVolumeColor = ContextCompat.getColor(context, R.color.blood_volume_color)
val bloodOxygenation = ContextCompat.getColor(context, R.color.blood_oxygenation_color)
binding.surface.suspendUpdates {
xAxes { numericAxis {
visibleRange = DoubleRange(0.0, 10.0)
autoRange = Never
drawMinorGridLines = false
drawMajorBands = false
visibility = View.GONE
}}
yAxes {
axis(generateYAxis(ECG_ID, dataProvider.ecgHeartRateRange))
axis(generateYAxis(BLOOD_PRESSURE_ID, dataProvider.bloodPressureRange))
axis(generateYAxis(BLOOD_VOLUME_ID, dataProvider.bloodVolumeRange))
axis(generateYAxis(BLOOD_OXYGENATION_ID, dataProvider.bloodOxygenationRange))
}
renderableSeries {
rSeries(generateLineSeries(ECG_ID, ecgDataSeries, heartRateColor))
rSeries(generateLineSeries(ECG_ID, ecgSweepDataSeries, heartRateColor))
rSeries(generateScatterForLastAppendedPoint(ECG_ID, lastEcgSweepDataSeries))
rSeries(generateLineSeries(BLOOD_PRESSURE_ID, bloodPressureDataSeries, bloodPressureColor))
rSeries(generateLineSeries(BLOOD_PRESSURE_ID, bloodPressureSweepDataSeries, bloodPressureColor))
rSeries(generateScatterForLastAppendedPoint(BLOOD_PRESSURE_ID, lastBloodPressureDataSeries))
rSeries(generateLineSeries(BLOOD_VOLUME_ID, bloodVolumeDataSeries, bloodVolumeColor))
rSeries(generateLineSeries(BLOOD_VOLUME_ID, bloodVolumeSweepDataSeries, bloodVolumeColor))
rSeries(generateScatterForLastAppendedPoint(BLOOD_VOLUME_ID, lastBloodVolumeDataSeries))
rSeries(generateLineSeries(BLOOD_OXYGENATION_ID, bloodOxygenationDataSeries, bloodOxygenation))
rSeries(generateLineSeries(BLOOD_OXYGENATION_ID, bloodOxygenationSweepDataSeries, bloodOxygenation))
rSeries(generateScatterForLastAppendedPoint(BLOOD_OXYGENATION_ID, lastBloodOxygenationSweepDataSeries))
}
layoutManager = DefaultLayoutManager.Builder()
.setRightOuterAxesLayoutStrategy(RightAlignedOuterVerticallyStackedYAxisLayoutStrategy())
.build()
}
}
private fun updateIndicators(time: Long) {
binding.heartRateIndicator.heartIcon.visibility = if (time % 2 == 0L) View.VISIBLE else View.INVISIBLE
if (time % 5 == 0L) {
indicatorsProvider.update()
binding.heartRateIndicator.bpmValueLabel.text = indicatorsProvider.bpmValue
binding.bloodPressureIndicator.bloodPressureValue.text = indicatorsProvider.bpValue
binding.bloodPressureIndicator.bloodPressureBar.progress = indicatorsProvider.bpbValue
binding.bloodVolumeIndicator.bloodVolumeValueLabel.text = indicatorsProvider.bvValue
binding.bloodVolumeIndicator.svBar1.progress = indicatorsProvider.bvBar1Value
binding.bloodVolumeIndicator.svBar2.progress = indicatorsProvider.bvBar2Value
binding.bloodOxygenationIndicator.spoValueLabel.text = indicatorsProvider.spoValue
binding.bloodOxygenationIndicator.spoClockLabel.text = indicatorsProvider.spoClockValue
}
}
private fun generateYAxis(id: String, visibleRange: DoubleRange): NumericAxis {
return NumericAxis(requireContext()).apply {
axisId = id
visibility = View.GONE
this.visibleRange = visibleRange
autoRange = Never
drawMajorBands = false
drawMinorGridLines = false
drawMajorGridLines = false
}
}
private fun generateLineSeries(yAxisId: String, ds: IDataSeries<*, *>, color: Int): IRenderableSeries {
return FastLineRenderableSeries().apply {
dataSeries = ds
this.yAxisId = yAxisId
this.strokeStyle = SolidPenStyle(color)
paletteProvider = DimTracePaletteProvider()
}
}
private fun generateScatterForLastAppendedPoint(yAxisId: String, ds: IDataSeries<*, *>): IRenderableSeries {
return XyScatterRenderableSeries().apply {
dataSeries = ds
this.yAxisId = yAxisId
ellipsePointMarker {
setSize(4)
fillStyle = SolidBrushStyle(ColorUtil.White)
strokeStyle = SolidPenStyle(ColorUtil.White)
}
}
}
private fun newDataSeries(fifoCapacity: Int): XyDataSeries<Double, Double> {
return XyDataSeries<Double, Double>().apply {
this.fifoCapacity = fifoCapacity
acceptsUnsortedData = true
}
}
private class RightAlignedOuterVerticallyStackedYAxisLayoutStrategy : VerticalAxisLayoutStrategy() {
override fun measureAxes(availableWidth: Int, availableHeight: Int, chartLayoutState: ChartLayoutState) {
for (i in 0 until axes.size) {
val axis = axes[i]
axis.updateAxisMeasurements()
chartLayoutState.rightOuterAreaSize = max(getRequiredAxisSize(axis.axisLayoutState), chartLayoutState.rightOuterAreaSize)
}
}
override fun layoutAxes(left: Int, top: Int, right: Int, bottom: Int) {
val size = axes.size
val height = bottom - top
val axisHeight = height / size
var topPlacement = top
for (i in 0 until size) {
val axis = axes[i]
val axisLayoutState = axis.axisLayoutState
val bottomPlacement = (topPlacement + axisHeight).toFloat().roundToInt()
axis.layoutArea(left, topPlacement, left + getRequiredAxisSize(axisLayoutState), bottomPlacement)
topPlacement = bottomPlacement
}
}
}
private class DimTracePaletteProvider : PaletteProviderBase<XyRenderableSeriesBase>(XyRenderableSeriesBase::class.java), IStrokePaletteProvider {
private val colors = IntegerValues()
private val startOpacity = 0.2
private val diffOpacity = 1 - startOpacity
override fun getStrokeColors(): IntegerValues = colors
override fun update() {
val defaultColor = renderableSeries!!.strokeStyle.color
val size = renderableSeries!!.currentRenderPassData.pointsCount()
colors.setSize(size)
val colorsArray = colors.itemsArray
for (i in 0 until size) {
val faction = i / size.toDouble()
val opacity = (startOpacity + faction * diffOpacity).toFloat()
colorsArray[i] = ColorUtil.argb(defaultColor, opacity)
}
}
}
companion object {
private const val FIFO_CAPACITY = 7850
private const val ECG_ID = "ecgId"
private const val BLOOD_PRESSURE_ID = "bloodPressureId"
private const val BLOOD_VOLUME_ID = "bloodVolumeId"
private const val BLOOD_OXYGENATION_ID = "bloodOxygenationId"
}
}
//******************************************************************************
// 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.featuredApps.medicalCharts.vitalSignsMonitor;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
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.utility.ColorUtil;
import com.scichart.examples.R;
import com.scichart.examples.databinding.ExampleVitalSignsMonitorFragmentBinding;
import com.scichart.examples.fragments.base.ShowcaseExampleBaseFragment;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
public class VitalSignsMonitorShowcaseFragment extends ShowcaseExampleBaseFragment<ExampleVitalSignsMonitorFragmentBinding> {
private static final int FIFO_CAPACITY = 7850;
private static final String ECG_ID = "ecgId";
private static final String BLOOD_PRESSURE_ID = "bloodPressureId";
private static final String BLOOD_VOLUME_ID = "bloodVolumeId";
private static final String BLOOD_OXYGENATION_ID = "bloodOxygenationId";
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();
private final EcgDataBatch dataBatch = new EcgDataBatch();
@NonNull
@Override
protected ExampleVitalSignsMonitorFragmentBinding inflateBinding(@NonNull LayoutInflater inflater) {
return ExampleVitalSignsMonitorFragmentBinding.inflate(inflater);
}
@Override
protected void initExample(@NonNull ExampleVitalSignsMonitorFragmentBinding binding) {
final DefaultVitalSignsDataProvider dataProvider = new DefaultVitalSignsDataProvider(requireContext());
setUpChart(dataProvider);
dataProvider.getData().buffer(50, TimeUnit.MILLISECONDS).doOnNext(ecgData -> {
if (ecgData.isEmpty()) return;
dataBatch.updateData(ecgData);
UpdateSuspender.using(binding.surface, () -> {
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 setUpChart(DefaultVitalSignsDataProvider dataProvider) {
final NumericAxis xAxis = sciChartBuilder.newNumericAxis()
.withVisibleRange(0, 10)
.withAutoRangeMode(AutoRange.Never)
.withDrawMinorGridLines(false)
.withDrawMajorBands(false)
.withVisibility(View.GONE)
.build();
final Context context = requireContext();
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);
final SciChartSurface surface = binding.surface;
UpdateSuspender.using(surface, () -> {
Collections.addAll(surface.getXAxes(), xAxis);
Collections.addAll(surface.getYAxes(),
generateYAxis(ECG_ID, dataProvider.getEcgHeartRateRange()),
generateYAxis(BLOOD_PRESSURE_ID, dataProvider.getBloodPressureRange()),
generateYAxis(BLOOD_VOLUME_ID, dataProvider.getBloodVolumeRange()),
generateYAxis(BLOOD_OXYGENATION_ID, dataProvider.getBloodOxygenationRange())
);
Collections.addAll(surface.getRenderableSeries(),
generateLineSeries(ECG_ID, ecgDataSeries, heartRateColor),
generateLineSeries(ECG_ID, ecgSweepDataSeries, heartRateColor),
generateScatterForLastAppendedPoint(ECG_ID, lastEcgSweepDataSeries),
generateLineSeries(BLOOD_PRESSURE_ID, bloodPressureDataSeries, bloodPressureColor),
generateLineSeries(BLOOD_PRESSURE_ID, bloodPressureSweepDataSeries, bloodPressureColor),
generateScatterForLastAppendedPoint(BLOOD_PRESSURE_ID, lastBloodPressureDataSeries),
generateLineSeries(BLOOD_VOLUME_ID, bloodVolumeDataSeries, bloodVolumeColor),
generateLineSeries(BLOOD_VOLUME_ID, bloodVolumeSweepDataSeries, bloodVolumeColor),
generateScatterForLastAppendedPoint(BLOOD_VOLUME_ID, lastBloodVolumeDataSeries),
generateLineSeries(BLOOD_OXYGENATION_ID, bloodOxygenationDataSeries, bloodOxygenation),
generateLineSeries(BLOOD_OXYGENATION_ID, bloodOxygenationSweepDataSeries, bloodOxygenation),
generateScatterForLastAppendedPoint(BLOOD_OXYGENATION_ID, lastBloodOxygenationSweepDataSeries)
);
surface.setLayoutManager(new DefaultLayoutManager.Builder().setRightOuterAxesLayoutStrategy(new RightAlignedOuterVerticallyStackedYAxisLayoutStrategy()).build());
});
}
private void updateIndicators(long time) {
binding.heartRateIndicator.heartIcon.setVisibility(time % 2 == 0 ? View.VISIBLE : View.INVISIBLE);
if (time % 5 == 0) {
indicatorsProvider.update();
binding.heartRateIndicator.bpmValueLabel.setText(indicatorsProvider.getBpmValue());
binding.bloodPressureIndicator.bloodPressureValue.setText(indicatorsProvider.getBpValue());
binding.bloodPressureIndicator.bloodPressureBar.setProgress(indicatorsProvider.getBpbValue());
binding.bloodVolumeIndicator.bloodVolumeValueLabel.setText(indicatorsProvider.getBvValue());
binding.bloodVolumeIndicator.svBar1.setProgress(indicatorsProvider.getBvBar1Value());
binding.bloodVolumeIndicator.svBar2.setProgress(indicatorsProvider.getBvBar2Value());
binding.bloodOxygenationIndicator.spoValueLabel.setText(indicatorsProvider.getSpoValue());
binding.bloodOxygenationIndicator.spoClockLabel.setText(indicatorsProvider.getSpoClockValue());
}
}
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, @ColorInt Integer color) {
return sciChartBuilder.newLineSeries()
.withDataSeries(ds)
.withYAxisId(yAxisId)
.withStrokeStyle(color)
.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);
ds.setAcceptsUnsortedData(true);
return ds;
}
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();
chartLayoutState.rightOuterAreaSize = Math.max(getRequiredAxisSize(axis.getAxisLayoutState()), 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();
for (int i = 0; i < size; i++) {
final double faction = i / (double)size;
final float opacity = (float) (startOpacity + faction * diffOpacity);
colorsArray[i] = ColorUtil.argb(defaultColor, opacity);
}
}
}
}