SciChart® the market leader in Fast WPF Charts, WPF 3D Charts, iOS Chart, Android Chart and JavaScript Chart Components
Please note! These examples are new to SciChart iOS v4 release! SciChart’s OpenGL ES and Metal iOS and Metal macOS Chart library ships with hundred of Objective-C and Swift iOS&macOS Chart Examples which you can browse, play with and view the source-code. All of this is possible with the new and improved SciChart iOS Examples Suite and demo application for Mac, which ships as part of the SciChart SDK.
The Vital Signs demo showcases how to use SciChart iOS Charts in a medical context.
Four channels of data are simulated, showing how SciChart iOS can be used to draw real-time, high performance ECG/EKG charts & graphs to monitor blood pressure, SPO2 blood oxygen, and volumetric flow enabling you to create medical apps using an iPad or iPhone device.
SciChart helps you shortcut development of medical applications by providing rich, real time, high performance & reliable iOS 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 iOS Chart library.
The Swift and Objective-C source code for the iOS and macOS Multiple Axis Demo example is included below (Scroll down!).
Did you know that we have the source code for all our example available for free on Github?
Clone the SciChart.iOS.Examples from Github.
Also the SciChart iOS and Scichart macOS Trials contain the full source for the examples (link below).
//******************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2020. All rights reserved.
//
// Web: http://www.scichart.com
// Support: support@scichart.com
// Sales: sales@scichart.com
//
// DefaultVitalSignsDataProvider.swift 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.
//******************************************************************************
import Foundation
class DefaultVitalSignsDataProvider: DataProviderBase<VitalSignsData> {
// 1. Heart rate or pulse rate (ECG HR)
// 2. Blood Pressure (NI BP)
// 3. Blood Volume (SV ml)
// 4. Blood Oxygenation (SPo2)
let SAMPLE_RATE: Double = 800
let ECG_TRACES = "EcgTraces.csv"
private var currentIndex = 0
private var totalIndex = 0
private var isATrace = false
let xValues = SCIDoubleValues()
let ecgHeartRate = SCIDoubleValues()
let bloodPressure = SCIDoubleValues()
let bloodVolume = SCIDoubleValues()
let bloodOxygenation = SCIDoubleValues()
init() {
super.init(dispatchTimeInterval: .microseconds(1000))
do {
let rawData = try String.init(contentsOfFile: SCDDataManager.getBundleFilePath(from: ECG_TRACES), encoding: .utf8)
let lines = rawData.components(separatedBy: "\n")
for i in 0 ..< lines.count - 1 {
let split = lines[i].components(separatedBy: ",") as [NSString]
xValues.add(split[0].doubleValue)
ecgHeartRate.add(split[1].doubleValue)
bloodPressure.add(split[2].doubleValue)
bloodVolume.add(split[3].doubleValue)
bloodOxygenation.add(split[4].doubleValue)
}
} catch {
print("Load ECG Error: Failed to load ECG. \(error.localizedDescription)")
}
}
override func onNext() -> VitalSignsData {
if currentIndex >= xValues.count {
currentIndex = 0;
}
let time = (Double(totalIndex) / SAMPLE_RATE).truncatingRemainder(dividingBy: 10)
let ecgHeartRate = self.ecgHeartRate.getValueAt(currentIndex)
let bloodPressure = self.bloodPressure.getValueAt(currentIndex)
let bloodVolume = self.bloodVolume.getValueAt(currentIndex)
let bloodOxygenation = self.bloodOxygenation.getValueAt(currentIndex)
let data = VitalSignsData(xValue: time,
ecgHeartRate: ecgHeartRate,
bloodPressure: bloodPressure,
bloodVolume: bloodVolume,
bloodOxygenation: bloodOxygenation,
isATrace: isATrace)
currentIndex += 1
totalIndex += 1
if totalIndex % 8000 == 0 {
isATrace = !isATrace
}
return data;
}
}
struct VitalSignsData {
let xValue: Double
let ecgHeartRate: Double
let bloodPressure: Double
let bloodVolume: Double
let bloodOxygenation: Double
let isATrace: Bool
}
//******************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2020. All rights reserved.
//
// Web: http://www.scichart.com
// Support: support@scichart.com
// Sales: sales@scichart.com
//
// EcgDataBatch.swift 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.
//******************************************************************************
import Foundation
class EcgDataBatch {
let xValues = SCIDoubleValues()
let ecgHeartRateValuesA = SCIDoubleValues()
let bloodPressureValuesA = SCIDoubleValues()
let bloodVolumeValuesA = SCIDoubleValues()
let bloodOxygenationA = SCIDoubleValues()
let ecgHeartRateValuesB = SCIDoubleValues()
let bloodPressureValuesB = SCIDoubleValues()
let bloodVolumeValuesB = SCIDoubleValues()
let bloodOxygenationB = SCIDoubleValues()
var lastVitalSignsData: VitalSignsData!
func updateData(_ vitalSignsDataList: [VitalSignsData]) {
xValues.clear()
ecgHeartRateValuesA.clear()
ecgHeartRateValuesB.clear()
bloodPressureValuesA.clear()
bloodPressureValuesB.clear()
bloodVolumeValuesA.clear()
bloodVolumeValuesB.clear()
bloodOxygenationA.clear()
bloodOxygenationB.clear()
let count = vitalSignsDataList.count
for i in 0 ..< count {
let vitalSignsData = vitalSignsDataList[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[count - 1]
}
}
//******************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2020. All rights reserved.
//
// Web: http://www.scichart.com
// Support: support@scichart.com
// Sales: sales@scichart.com
//
// RightAlignedOuterVerticallyStackedYAxisLayoutStrategy.swift 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.
//******************************************************************************
import Foundation
class RightAlignedOuterVerticallyStackedYAxisLayoutStrategy: SCIVerticalAxisLayoutStrategy {
override func measureAxes(withAvailableWidth width: CGFloat, height: CGFloat, andChartLayoutState chartLayoutState: SCIChartLayoutState) {
for i in 0 ..< axes.count {
let axis = axes[i] as! ISCIAxis
axis.updateMeasurements()
let axisLayoutState = axis.axisLayoutState
chartLayoutState.rightOuterAreaSize = max(SCIVerticalAxisLayoutStrategy.getRequiredAxisSize(from: axisLayoutState), chartLayoutState.rightOuterAreaSize)
}
}
override func layout(withLeft left: CGFloat, top: CGFloat, right: CGFloat, bottom: CGFloat) {
let count = axes.count
let height = bottom - top
let axisHeight = height / CGFloat(count)
var topPlacement = top
for i in 0 ..< count {
let axis = axes[i] as! ISCIAxis
let axisLayoutState = axis.axisLayoutState
let bottomPlacement = round(topPlacement + axisHeight)
axis.layoutArea(withLeft: left, top: topPlacement, right: left + SCIVerticalAxisLayoutStrategy.getRequiredAxisSize(from: axisLayoutState), bottom: bottomPlacement)
topPlacement = bottomPlacement
}
}
}
//******************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2020. All rights reserved.
//
// Web: http://www.scichart.com
// Support: support@scichart.com
// Sales: sales@scichart.com
//
// SCDDimTracePaletteProvider.m 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.
//******************************************************************************
#import "SCDDimTracePaletteProvider.h"
@implementation SCDDimTracePaletteProvider {
SCIUnsignedIntegerValues *_colors;
double _startOpacity;
double _diffOpacity;
}
- (instancetype)init {
self = [super initWithRenderableSeriesType:SCIFastLineRenderableSeries.class];
if (self) {
_colors = [SCIUnsignedIntegerValues new];
_startOpacity = 0.2;
_diffOpacity = 1 - _startOpacity;
}
return self;
}
- (void)update {
unsigned int defaultColor = self.renderableSeries.strokeStyle.color.colorARGBCode;
NSInteger count = self.renderableSeries.currentRenderPassData.pointsCount;
_colors.count = count;
unsigned int *colorsArray = _colors.itemsArray;
for (int i = 0; i < count; i++) {
double fraction = (double)i / count;
double opacity = _startOpacity + fraction * _diffOpacity;
unsigned int color = [SCIColor argb:defaultColor withOpacity:opacity];
colorsArray[i] = color;
}
}
- (SCIUnsignedIntegerValues *)strokeColors {
return _colors;
}
@end
//******************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2020. All rights reserved.
//
// Web: http://www.scichart.com
// Support: support@scichart.com
// Sales: sales@scichart.com
//
// SCDVitalSignsLayoutViewController.m 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.
//******************************************************************************
#import "SCDVitalSignsLayoutViewController.h"
#import "SCIStackView.h"
@implementation SCDVitalSignsLayoutViewController {
SCIView *_topView;
SCIStackView *topStackView;
SCIView *_bottomView;
SCIStackView *bottomStackView;
BOOL isPortrait;
NSLayoutConstraint *_topViewHeightAnchorPortrait;
NSLayoutConstraint *_topViewWidthAnchorLandscape;
SCD_ECGView *_ecgView;
SCD_NIBPView *_nibpView;
SCD_SVView *_svView;
SCD_SPO2View *_spo2View;
}
- (void)tryUpdateChartTheme:(SCIChartTheme)theme {
[SCIThemeManager applyTheme:theme toThemeable:self.surface];
}
- (void)loadView {
[super loadView];
self.view = [SCIView new];
self.view.autoresizingMask = SCIAutoresizingFlexible;
_topView = [self p_SCD_createTopView];
_topView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:_topView];
_bottomView = [self p_SCD_createBottomView];
_bottomView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:_bottomView];
_surface = [[self.associatedType alloc] initWithFrame:CGRectMake(0, 0, 1, 1)];
_surface.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:_surface];
#if TARGET_OS_OSX
isPortrait = NO;
#elif TARGET_OS_IOS
isPortrait = UIInterfaceOrientationIsPortrait(UIApplication.sharedApplication.statusBarOrientation);
#endif
[self p_SCD_updateConstraints];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.view.platformBackgroundColor = _surface.backgroundBrushStyle.color;
}
#if TARGET_OS_IOS
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
isPortrait = size.height > size.width;
[self p_SCD_updateConstraints];
}
#endif
- (SCIImageView *)heartImageView {
return _ecgView.heartImageView;
}
- (SCILabel *)bpmValueLabel {
return _ecgView.bpmValueLabel;
}
- (SCDStepProgressBar *)bpBar {
return _nibpView.bpBar;
}
- (SCILabel *)bpValueLabel {
return _nibpView.bpValueLabel;
}
- (SCILabel *)bvValueLabel {
return _svView.bvValueLabel;
}
- (SCDStepProgressBar *)svBar1 {
return _svView.svBar1;
}
- (SCDStepProgressBar *)svBar2 {
return _svView.svBar2;
}
- (SCILabel *)spoClockValueLabel {
return _spo2View.spoClockValueLabel;
}
- (SCILabel *)spoValueLabel {
return _spo2View.spoValueLabel;
}
- (void)p_SCD_updateConstraints {
[self p_SCD_removeAllConstraints];
[self p_SCD_updateStackViewsAxis];
//Portrait
_topViewHeightAnchorPortrait = [_topView.heightAnchor constraintEqualToConstant:131];
_topViewHeightAnchorPortrait.active = isPortrait;
[_topView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor].active = isPortrait;
[_surface.topAnchor constraintEqualToAnchor:_topView.bottomAnchor].active = isPortrait;
[_surface.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor].active = isPortrait;
[_surface.bottomAnchor constraintEqualToAnchor:_bottomView.topAnchor].active = isPortrait;
//Landscape
_topViewWidthAnchorLandscape = [_topView.widthAnchor constraintEqualToConstant:200];
_topViewWidthAnchorLandscape.active = !isPortrait;
[_surface.topAnchor constraintEqualToAnchor:self.view.topAnchor].active = !isPortrait;
[_surface.trailingAnchor constraintEqualToAnchor:_topView.leadingAnchor].active = !isPortrait;
[_surface.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor].active = !isPortrait;
[_bottomView.topAnchor constraintEqualToAnchor:_topView.bottomAnchor].active = !isPortrait;
//Common
[_topView.topAnchor constraintEqualToAnchor:self.view.topAnchor].active = YES;
[_topView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor].active = YES;
[_surface.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor].active = YES;
[_bottomView.heightAnchor constraintEqualToAnchor:_topView.heightAnchor].active = YES;
[_bottomView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor].active = YES;
[_bottomView.trailingAnchor constraintEqualToAnchor:_topView.trailingAnchor].active = YES;
[_bottomView.leadingAnchor constraintEqualToAnchor:_topView.leadingAnchor].active = YES;
}
- (void)p_SCD_updateStackViewsAxis {
topStackView.axis = [self p_SCD_getStackViewConstraintAxis];
bottomStackView.axis = [self p_SCD_getStackViewConstraintAxis];
}
- (void)p_SCD_removeAllConstraints {
for (NSLayoutConstraint *constraint in self.view.constraints) {
if (constraint != nil) {
[self.view removeConstraint:constraint];
}
}
if (_topViewHeightAnchorPortrait != nil) {
[_topView removeConstraint:_topViewHeightAnchorPortrait];
}
if (_topViewWidthAnchorLandscape != nil) {
[_topView removeConstraint:_topViewWidthAnchorLandscape];
}
}
- (SCIView *)p_SCD_createTopView {
_ecgView = [self p_SCD_createECGView];
_nibpView = [self p_SCD_createNIBPView];
topStackView = [SCIStackView new];
[topStackView addArrangedSubview:_ecgView];
[topStackView addArrangedSubview:_nibpView];
return [self createContainerViewWithStackView:topStackView];
}
- (SCIView *)p_SCD_createBottomView {
_svView = [self p_SCD_createSVView];
_spo2View = [self p_SCD_createSPO2View];
bottomStackView = [SCIStackView new];
[bottomStackView addArrangedSubview:_svView];
[bottomStackView addArrangedSubview:_spo2View];
return [self createContainerViewWithStackView:bottomStackView];
}
- (SCD_ECGView *)p_SCD_createECGView {
SCIColor *color = [SCIColor fromARGBColorCode:0xFF42B649];
SCILabel *ecgLabel = [SCILabel new];
ecgLabel.text = @"ECG";
ecgLabel.textColor = color;
ecgLabel.font = [SCIFont systemFontOfSize:18];
SCIImage *image = [SCIImage imageNamed:@"icon.heart" inBundleWithIdentifier:@"com.scichart.examples.sources"];
SCIImageView *heartImageView = [SCIImageView imageViewWithImage:image];
heartImageView.translatesAutoresizingMaskIntoConstraints = NO;
[heartImageView addConstraints:@[
[heartImageView.widthAnchor constraintEqualToConstant:24],
[heartImageView.heightAnchor constraintEqualToConstant:24],
]];
SCILabel *bottomLeftLabel = [SCILabel new];
bottomLeftLabel.numberOfLines = 0;
bottomLeftLabel.text = @"V1- 1.4MM\nST |+ 0.6 ||+ 0.9";
bottomLeftLabel.textColor = color;
bottomLeftLabel.font = [SCIFont systemFontOfSize:10];
SCILabel *bottomRightLabel = [SCILabel new];
bottomRightLabel.text = @"87";
bottomRightLabel.textColor = color;
bottomRightLabel.font = [SCIFont italicSystemFontOfSize:36];
return [[SCD_ECGView alloc] initWithTopLeftView:ecgLabel topRightView:heartImageView bottomLeftView:bottomLeftLabel bottomRightView:bottomRightLabel];
}
- (SCD_NIBPView *)p_SCD_createNIBPView {
SCIColor *color = [SCIColor fromARGBColorCode:0xFFFFFF00];
SCILabel *topLeftLabel = [SCILabel new];
topLeftLabel.text = @"NIBP";
topLeftLabel.textColor = color;
topLeftLabel.font = [SCIFont systemFontOfSize:18];
SCILabel *topRightLabel = [SCILabel new];
topRightLabel.numberOfLines = 0;
topRightLabel.text = @"AUTO\n145/95";
topRightLabel.textColor = color;
topRightLabel.font = [SCIFont systemFontOfSize:18];
SCDStepProgressBar *bpBar = [SCDStepProgressBar new];
bpBar.translatesAutoresizingMaskIntoConstraints = NO;
[bpBar addConstraints:@[
[bpBar.heightAnchor constraintEqualToConstant:20],
]];
SCILabel *bottomRightLabel = [SCILabel new];
bottomRightLabel.text = @"87";
bottomRightLabel.textColor = color;
bottomRightLabel.font = [SCIFont italicSystemFontOfSize:36];
return [[SCD_NIBPView alloc] initWithTopLeftView:topLeftLabel topRightView:topRightLabel bottomLeftView:bpBar bottomRightView:bottomRightLabel];
}
- (SCD_SVView *)p_SCD_createSVView {
SCIColor *color = [SCIColor fromARGBColorCode:0xFFB0C4DE];
SCILabel *topLeftLabel = [SCILabel new];
topLeftLabel.text = @"SV";
topLeftLabel.textColor = color;
topLeftLabel.font = [SCIFont systemFontOfSize:18];
SCILabel *topRightLabel = [SCILabel new];
topRightLabel.numberOfLines = 0;
topRightLabel.text = @"ML 100\n%***** 55";
topRightLabel.textColor = color;
topRightLabel.font = [SCIFont systemFontOfSize:18];
SCDStepProgressBar *svBar1 = [SCDStepProgressBar new];
svBar1.translatesAutoresizingMaskIntoConstraints = NO;
[svBar1 addConstraints:@[
[svBar1.widthAnchor constraintEqualToConstant:20]
]];
SCDStepProgressBar *svBar2 = [SCDStepProgressBar new];
svBar2.translatesAutoresizingMaskIntoConstraints = NO;
[svBar2 addConstraints:@[
[svBar2.widthAnchor constraintEqualToConstant:20]
]];
SCIStackView *stackView = [SCIStackView new];
stackView.axis = SCILayoutConstraintAxisHorizontal;
stackView.distribution = SCIStackViewDistributionFillEqually;
stackView.spacing = 3;
[stackView addArrangedSubview:svBar1];
[stackView addArrangedSubview:svBar2];
SCILabel *bottomRightLabel = [SCILabel new];
bottomRightLabel.text = @"87";
bottomRightLabel.textColor = color;
bottomRightLabel.font = [SCIFont italicSystemFontOfSize:36];
return [[SCD_SVView alloc] initWithTopLeftView:topLeftLabel topRightView:topRightLabel bottomLeftView:stackView bottomRightView:bottomRightLabel];
}
- (SCD_SPO2View *)p_SCD_createSPO2View {
SCIColor *color = [SCIColor fromARGBColorCode:0xFF6495ED];
SCILabel *topLeftLabel = [SCILabel new];
topLeftLabel.text = @"SPO2";
topLeftLabel.textColor = color;
topLeftLabel.font = [SCIFont systemFontOfSize:18];
SCILabel *topRightLabel = [SCILabel new];
topRightLabel.text = @"14:35";
topRightLabel.textColor = color;
topRightLabel.font = [SCIFont systemFontOfSize:14];
SCILabel *bottomLeftLabel = [SCILabel new];
bottomLeftLabel.numberOfLines = 0;
bottomLeftLabel.text = @"71-\nRESP";
bottomLeftLabel.textColor = color;
bottomLeftLabel.font = [SCIFont systemFontOfSize:10];
SCILabel *bottomRightLabel = [SCILabel new];
bottomRightLabel.text = @"87";
bottomRightLabel.textColor = color;
bottomRightLabel.font = [SCIFont italicSystemFontOfSize:36];
return [[SCD_SPO2View alloc] initWithTopLeftView:topLeftLabel topRightView:topRightLabel bottomLeftView:bottomLeftLabel bottomRightView:bottomRightLabel];
}
- (SCILayoutConstraintAxis)p_SCD_getStackViewConstraintAxis {
return isPortrait ? SCILayoutConstraintAxisHorizontal : SCILayoutConstraintAxisVertical;
}
- (SCIView *)createContainerViewWithStackView:(SCIStackView *)stackView {
stackView.distribution = SCIStackViewDistributionFillEqually;
stackView.axis = SCILayoutConstraintAxisHorizontal;
stackView.spacing = 0;
SCIView *container = [SCIView new];
stackView.translatesAutoresizingMaskIntoConstraints = NO;
[container addSubview:stackView];
[container addConstraints:@[
[stackView.leadingAnchor constraintEqualToAnchor:container.leadingAnchor],
[stackView.topAnchor constraintEqualToAnchor:container.topAnchor],
[stackView.trailingAnchor constraintEqualToAnchor:container.trailingAnchor],
[stackView.bottomAnchor constraintEqualToAnchor:container.bottomAnchor]
]];
return container;
}
@end
//******************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2020. All rights reserved.
//
// Web: http://www.scichart.com
// Support: support@scichart.com
// Sales: sales@scichart.com
//
// VitalSignsIndicatorsProvider.swift 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.
//******************************************************************************
import Foundation
class VitalSignsIndicatorsProvider {
private let timeFormat = "HH:mm"
private let BPM_VALUES = ["67", "69", "72", "74"]
private let BP_VALUES = ["120/70", "115/70", "115/75", "120/75"]
private let BPB_VALUES = [5, 6, 7]
private let BV_VALUES = ["13.1", "13.2", "13.3", "13.0"]
private let BVB_VALUES = [9, 10, 11]
private let BO_VALUES = ["93", "95", "96", "97"]
var bpmValue = ""
var bpValue = ""
var bpbValue = 0
var bvValue = ""
var bvBar1Value = 0
var bvBar2Value = 0
var spoValue = ""
var spoClockValue = ""
func 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 func randomString(_ values: [String]) -> String {
return randomElement(values) ?? ""
}
private func randomInt(_ values: [Int]) -> Int {
return randomElement(values) ?? 0
}
private func randomElement<T>(_ values: [T]) -> T? {
return values.randomElement()
}
private func getTimeString() -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = timeFormat
return dateFormatter.string(from: Date())
}
}
//******************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2020. All rights reserved.
//
// Web: http://www.scichart.com
// Support: support@scichart.com
// Sales: sales@scichart.com
//
// VitalSignsMonitorChartView.swift 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.
//******************************************************************************
import RxSwift
class VitalSignsMonitorChartView: SCDVitalSignsLayoutViewController<SCIChartSurface> {
override var associatedType: AnyClass { return SCIChartSurface.self }
static let FIFO_CAPACITY = 7850
private let ecgDataSeries = newDataSeries(FIFO_CAPACITY)
private let ecgSweepDataSeries = newDataSeries(FIFO_CAPACITY)
private let bloodPressureDataSeries = newDataSeries(FIFO_CAPACITY)
private let bloodPressureSweepDataSeries = newDataSeries(FIFO_CAPACITY)
private let bloodVolumeDataSeries = newDataSeries(FIFO_CAPACITY)
private let bloodVolumeSweepDataSeries = newDataSeries(FIFO_CAPACITY)
private let bloodOxygenationDataSeries = newDataSeries(FIFO_CAPACITY)
private let bloodOxygenationSweepDataSeries = newDataSeries(FIFO_CAPACITY)
private let lastEcgSweepDataSeries = newDataSeries(1)
private let lastBloodPressureDataSeries = newDataSeries(1)
private let lastBloodVolumeDataSeries = newDataSeries(1)
private let lastBloodOxygenationSweepDataSeries = newDataSeries(1)
private let indicatorsProvider = VitalSignsIndicatorsProvider()
private var dataBatch: EcgDataBatch = EcgDataBatch()
private var chartDisposable: Disposable?
private var indicatorsDisposable: Disposable?
deinit {
chartDisposable?.dispose()
indicatorsDisposable?.dispose()
}
override func initExample() {
chartDisposable?.dispose()
indicatorsDisposable?.dispose()
setUpChart()
setupIndicators()
let dataProvider = DefaultVitalSignsDataProvider()
chartDisposable = dataProvider
.getData()
.buffer(timeSpan: .milliseconds(16), count: Int.max, scheduler: MainScheduler.instance)
.do(onNext: { [weak self] ecgData in
guard let self = self else { return }
if ecgData.isEmpty { return }
self.dataBatch.updateData(ecgData)
SCIUpdateSuspender.usingWith(self.surface) {
self.updateDataSeries()
}
})
.subscribe()
updateIndicators(time: 0)
indicatorsDisposable = Observable
.interval(.seconds(1), scheduler: MainScheduler.instance)
.do(onNext: { [weak self] in
self?.updateIndicators(time: $0)
})
.subscribe()
}
private func setupIndicators() {
let progressBackgroundColor = #colorLiteral(red: 0.3921568627, green: 0.3921568627, blue: 0.3921568627, alpha: 1)
let bpBarStyle = SCDProgressBarStyle(isVertical: false, max: 10, spacing: 2, progressBackgroundColor: progressBackgroundColor, progressColor: SCIColor.fromARGBColorCode(0xFFFFFF00), barSize: 3)
bpBar.setStyle(bpBarStyle)
let svBarStyle = SCDProgressBarStyle(isVertical: true, max: 12, spacing: 2, progressBackgroundColor: .clear, progressColor: SCIColor.fromARGBColorCode(0xFFB0C4DE), barSize: 3)
svBar1.setStyle(svBarStyle)
svBar2.setStyle(svBarStyle)
}
private func updateIndicators(time: Int) {
heartImageView.isHidden = time % 2 == 0
if (time % 5 == 0) {
indicatorsProvider.update();
bpmValueLabel.text = indicatorsProvider.bpmValue
bpValueLabel.text = indicatorsProvider.bpValue
bpBar.setProgress(indicatorsProvider.bpbValue)
bvValueLabel.text = indicatorsProvider.bvValue
svBar1.setProgress(indicatorsProvider.bvBar1Value)
svBar2.setProgress(indicatorsProvider.bvBar2Value)
spoValueLabel.text = indicatorsProvider.spoValue
spoClockValueLabel.text = indicatorsProvider.spoClockValue
}
}
private func updateDataSeries() {
let xValues = dataBatch.xValues;
ecgDataSeries.append(x: xValues, y: dataBatch.ecgHeartRateValuesA)
ecgSweepDataSeries.append(x: xValues, y: dataBatch.ecgHeartRateValuesB)
bloodPressureDataSeries.append(x: xValues, y: dataBatch.bloodPressureValuesA)
bloodPressureSweepDataSeries.append(x: xValues, y: dataBatch.bloodPressureValuesB)
bloodOxygenationDataSeries.append(x: xValues, y: dataBatch.bloodOxygenationA)
bloodOxygenationSweepDataSeries.append(x: xValues, y: dataBatch.bloodOxygenationB)
bloodVolumeDataSeries.append(x: xValues, y: dataBatch.bloodVolumeValuesA)
bloodVolumeSweepDataSeries.append(x: xValues, y: dataBatch.bloodVolumeValuesB)
guard let lastVitalSignsData = dataBatch.lastVitalSignsData else {
return
}
let xValue = lastVitalSignsData.xValue
lastEcgSweepDataSeries.append(x: xValue, y: lastVitalSignsData.ecgHeartRate)
lastBloodPressureDataSeries.append(x: xValue, y: lastVitalSignsData.bloodPressure)
lastBloodOxygenationSweepDataSeries.append(x: xValue, y: lastVitalSignsData.bloodOxygenation)
lastBloodVolumeDataSeries.append(x: xValue, y: lastVitalSignsData.bloodVolume)
}
private func setUpChart() {
let xAxis = SCINumericAxis()
xAxis.visibleRange = SCIDoubleRange(min: 0, max: 10)
xAxis.autoRange = .never
xAxis.drawMinorGridLines = false
xAxis.drawMajorGridLines = false
xAxis.isVisible = false
let ecgId = "ecgId"
let bloodPressureId = "bloodPressureId"
let bloodVolumeId = "bloodVolumeId"
let bloodOxygenationId = "bloodOxygenationId"
let yAxisEcg: SCINumericAxis = generateYAxis(ecgId)
let yAxisPressure: SCINumericAxis = generateYAxis(bloodPressureId)
let yAxisVolume: SCINumericAxis = generateYAxis(bloodVolumeId)
let yAxisOxygenation: SCINumericAxis = generateYAxis(bloodOxygenationId)
let heartRateColor: UInt32 = 0xFF42B649
let bloodPressureColor: UInt32 = 0xFFFFFF00
let bloodVolumeColor: UInt32 = 0xFFB0C4DE
let bloodOxygenation: UInt32 = 0xFF6495ED
SCIUpdateSuspender.usingWith(surface) {
self.surface.xAxes.add(xAxis)
self.surface.yAxes.add(yAxisEcg)
self.surface.yAxes.add(yAxisPressure)
self.surface.yAxes.add(yAxisVolume)
self.surface.yAxes.add(yAxisOxygenation)
self.surface.renderableSeries.add(self.generateLineSeries(yAxisId: ecgId, dataSeries: self.ecgDataSeries, strokeStyle: SCISolidPenStyle(color: heartRateColor, thickness: 1)))
self.surface.renderableSeries.add(self.generateLineSeries(yAxisId: ecgId, dataSeries: self.ecgSweepDataSeries, strokeStyle: SCISolidPenStyle(color: heartRateColor, thickness: 1)))
self.surface.renderableSeries.add(self.generateScatterForLastAppendedPoint(yAxisId: ecgId, dataSeries: self.lastEcgSweepDataSeries))
self.surface.renderableSeries.add(self.generateLineSeries(yAxisId: bloodPressureId, dataSeries: self.bloodPressureDataSeries, strokeStyle: SCISolidPenStyle(color: bloodPressureColor, thickness: 1)))
self.surface.renderableSeries.add(self.generateLineSeries(yAxisId: bloodPressureId, dataSeries: self.bloodPressureSweepDataSeries, strokeStyle: SCISolidPenStyle(color: bloodPressureColor, thickness: 1)))
self.surface.renderableSeries.add(self.generateScatterForLastAppendedPoint(yAxisId: bloodPressureId, dataSeries: self.lastBloodPressureDataSeries))
self.surface.renderableSeries.add(self.generateLineSeries(yAxisId: bloodVolumeId, dataSeries: self.bloodVolumeDataSeries, strokeStyle: SCISolidPenStyle(color: bloodVolumeColor, thickness: 1)))
self.surface.renderableSeries.add(self.generateLineSeries(yAxisId: bloodVolumeId, dataSeries: self.bloodVolumeSweepDataSeries, strokeStyle: SCISolidPenStyle(color: bloodVolumeColor, thickness: 1)))
self.surface.renderableSeries.add(self.generateScatterForLastAppendedPoint(yAxisId: bloodVolumeId, dataSeries: self.lastBloodVolumeDataSeries))
self.surface.renderableSeries.add(self.generateLineSeries(yAxisId: bloodOxygenationId, dataSeries: self.bloodOxygenationDataSeries, strokeStyle: SCISolidPenStyle(color: bloodOxygenation, thickness: 1)))
self.surface.renderableSeries.add(self.generateLineSeries(yAxisId: bloodOxygenationId, dataSeries: self.bloodOxygenationSweepDataSeries, strokeStyle: SCISolidPenStyle(color: bloodOxygenation, thickness: 1)))
self.surface.renderableSeries.add(self.generateScatterForLastAppendedPoint(yAxisId: bloodOxygenationId, dataSeries: self.lastBloodOxygenationSweepDataSeries))
let layoutManager = SCIDefaultLayoutManager()
layoutManager.rightOuterAxisLayoutStrategy = RightAlignedOuterVerticallyStackedYAxisLayoutStrategy()
self.surface.layoutManager = layoutManager
}
}
private func generateScatterForLastAppendedPoint(yAxisId: String, dataSeries: ISCIDataSeries) -> ISCIRenderableSeries {
let pointMarker = SCIEllipsePointMarker()
pointMarker.size = CGSize(width: 4, height: 4)
pointMarker.strokeStyle = SCISolidPenStyle(color: .white, thickness: 1)
pointMarker.fillStyle = SCISolidBrushStyle(color: .white)
let scatterSeries = SCIXyScatterRenderableSeries()
scatterSeries.dataSeries = dataSeries
scatterSeries.yAxisId = yAxisId
scatterSeries.pointMarker = pointMarker
return scatterSeries
}
private func generateLineSeries(yAxisId: String, dataSeries: ISCIDataSeries, strokeStyle: SCIPenStyle) -> ISCIRenderableSeries {
let series = SCIFastLineRenderableSeries()
series.dataSeries = dataSeries
series.yAxisId = yAxisId
series.strokeStyle = strokeStyle
series.paletteProvider = SCDDimTracePaletteProvider()
return series
}
private func generateYAxis(_ id: String) -> SCINumericAxis {
let yAxis = SCINumericAxis()
yAxis.axisId = id
yAxis.growBy = SCIDoubleRange(min: 0.05, max: 0.05)
yAxis.autoRange = .always
yAxis.drawMajorBands = false
yAxis.drawMinorGridLines = false
yAxis.drawMajorGridLines = false
yAxis.axisAlignment = .right
yAxis.isVisible = false
return yAxis
}
private static func newDataSeries(_ fifoCapacity: Int) -> SCIXyDataSeries {
let dataSeries = SCIXyDataSeries(xType: .double, yType: .double)
dataSeries.fifoCapacity = fifoCapacity
dataSeries.acceptsUnsortedData = true
return dataSeries
}
}
//******************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2020. All rights reserved.
//
// Web: http://www.scichart.com
// Support: support@scichart.com
// Sales: sales@scichart.com
//
// SCDVitalSignsComponentView.m 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.
//******************************************************************************
#import "SCDVitalSignsComponentView.h"
#if TARGET_OS_OSX
#elif TARGET_OS_IOS
#import <UIKit/NSLayoutAnchor.h>
#endif
@implementation SCDVitalSignsComponentView {
SCIView *_topLeftView;
SCIView *_topRightView;
SCIView *_bottomLeftView;
SCIView *_bottomRightView;
}
- (instancetype)initWithTopLeftView:(SCIView *)topLeftView topRightView:(SCIView *)topRightView bottomLeftView:(SCIView *)bottomLeftView bottomRightView:(SCIView *)bottomRightView {
self = [super initWithFrame:CGRectZero];
if (self) {
_topLeftView = topLeftView;
_topRightView = topRightView;
_bottomLeftView = bottomLeftView;
_bottomRightView = bottomRightView;
[self p_SCD_setupViews];
}
return self;
}
- (void)p_SCD_setupViews {
for (SCIView *view in @[_topLeftView, _topRightView, _bottomLeftView, _bottomRightView]) {
view.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:view];
}
[self addConstraints:@[
[_topLeftView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:8],
[_topLeftView.topAnchor constraintEqualToAnchor:self.topAnchor constant:8],
[_topRightView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-8],
[_topRightView.topAnchor constraintEqualToAnchor:self.topAnchor constant:8],
[_bottomLeftView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:8],
[_bottomLeftView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor constant:-8],
[_bottomRightView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-8],
[_bottomRightView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor constant:0],
]];
}
@end