SciChart® the market leader in Fast WPF Charts, WPF 3D Charts, and now iOS Charting & Android Chart Components

Welcome to the SciChart Community Forums!

Please use the forums below to ask questions about SciChart. Take a moment to read our Question asking guidelines on how to ask a good question and our support policy. We also have a tag=SciChart on Stackoverflow.com where you can earn rep for your questions!

Please note: SciChart team will only answer questions from customers with active support subscriptions. Expired support questions will be ignored. If your support status shows incorrectly, contact us and we will be glad to help.

Answered
1
0

Hello,

I have been working with an application that plots real-time serial data using a FIFO buffer. I have started programming around the ECG-Monitor example as this is exactly what I am creating.

I have a device that broadcasts real-time ECG data via Bluetooth (HC-05), to be exact. I have paired the device and opened a SerialPort in my program to receive the data. My sampling rate is 256 Hz.

When I used a text file to simulate ECG data, it works perfectly well. However, when I use real-time data, there is a delay in charting that increases as the time increases. An easier way to understand this is, the chart continues plotting for a significant period of time even after I have switched off my hardware device.

I have then come to the conclusion that my data is being received at the rate that I want it to, but the plotting gets delayed at an increasing rate as the time increases.

I am currently using the Direct-X rendering type as this gives me a very smooth plot. I receive the data via SerialPort, write to an array and then to the FIFO buffer.

I’m attaching my code for the same.

namespace SciChart.Examples.Examples.SeeFeaturedApplication.ECGMonitor
{
    public class ECGMonitorViewModel : BaseViewModel
    {
    private Timer _timer;
    private IXyDataSeries<double, double> _series0;
    public static double[] _sourceData = new double[50000];
    private int _currentIndex = 0;
    private int _totalIndex = 0;
    private DoubleRange _yVisibleRange;
    private bool _isBeat;
    private int _heartRate;
    private bool _lastBeat;
    private DateTime _lastBeatTime;
    private ICommand _startCommand;
    private ICommand _stopCommand;
    private const double WindowSize = 5.0;
    private const int TimerInterval = 40;
    public static int counter = 0;
    public static SerialPort mySerialPort=new SerialPort("COM3",9600);

    public ECGMonitorViewModel()
    {
        mySerialPort.Open();
        ECGMonitorViewModel.mySerialPort.WriteLine("A");
        ECGMonitorViewModel.mySerialPort.WriteLine("F");

        _series0 = new XyDataSeries<double, double>() { FifoCapacity = 2460, AcceptsUnsortedData = true };

        YVisibleRange = new DoubleRange(-20, 500);
        _startCommand = new ActionCommand(OnExampleEnter);
        _stopCommand = new ActionCommand(OnExampleExit);
    }

    public ICommand StartCommand
    {
        get
        {
            return _startCommand;
        }
    }

    public ICommand StopCommand
    {
        get
        {
            return _stopCommand;
        }
    }

    public IXyDataSeries<double, double> EcgDataSeries
    {
        get
        {
            return _series0;
        }
        set
        {
            _series0 = value;
            OnPropertyChanged("EcgDataSeries");
        }
    }

    public DoubleRange YVisibleRange
    {
        get
        {
            return _yVisibleRange;
        }
        set
        {
            _yVisibleRange = value;
            OnPropertyChanged("YVisibleRange");
        }
    }

    public bool IsBeat
    {
        get
        {
            return _isBeat;
        }
        set
        {
            if (_isBeat != value)
            {
                _isBeat = value;
                OnPropertyChanged("IsBeat");
            }
        }
    }

    public int HeartRate
    {
        get { return _heartRate; }
        set
        {
            _heartRate = value;
            OnPropertyChanged("HeartRate");
        }
    }

    public void OnExampleExit()
    {
        if (_timer != null)
        {
            _timer.Stop();
            _timer.Elapsed -= TimerElapsed;
            _timer = null;
        }
    }

    public void OnExampleEnter()
    {
        _timer = new Timer(TimerInterval) { AutoReset = true };
        _timer.Elapsed += TimerElapsed;
        _timer.Start();
    }

    private void TimerElapsed(object sender, EventArgs e)
    {
        lock (this)
        {
            for (int i = 0; i < 10; i++)
            {
                AppendPoint(250);
            }

            if ((DateTime.Now - _lastBeatTime).TotalMilliseconds < 120) return;

            IsBeat = _series0.YValues[_series0.Count - 3] > 120 ||
                     _series0.YValues[_series0.Count - 5] > 120 ||
                     _series0.YValues[_series0.Count - 8] > 120;

            if (IsBeat && !_lastBeat)
            {
                HeartRate = (int)(60.0 / (DateTime.Now - _lastBeatTime).TotalSeconds);
                _lastBeatTime = DateTime.Now;
            }
        }
    }

    private void AppendPoint(double sampleRate)
    {
        if (_currentIndex >= _sourceData.Length)
        {
            _currentIndex = 0;
        }

        double voltage = _sourceData[_currentIndex];
        double time = _totalIndex / sampleRate %10;

        if(time==0.00)
        {
            voltage = double.NaN;
        }

        _series0.Append(time, voltage);

        _lastBeat = IsBeat;
        _currentIndex++;
        _totalIndex++;
    }

    public static void mySerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        while (mySerialPort.BytesToRead > 0)
        {
            int b;
            b = mySerialPort.ReadByte();
            _sourceData[counter] = b;
            counter++;
        }
    }
}
}

namespace SciChart.Examples.Examples.SeeFeaturedApplication.ECGMonitor
{
    public partial class ECGMonitorView : UserControl
    {
        public ECGMonitorView()
        {
            InitializeComponent();
            ECGMonitorViewModel.mySerialPort.DataReceived += new SerialDataReceivedEventHandler(ECGMonitorViewModel.mySerialPort_DataReceived);
        }
    }
}

The xaml code is as follows,

<UserControl
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
         xmlns:local="clr-namespace:SciChart.Examples.Examples.SeeFeaturedApplication.ECGMonitor"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:s="http://schemas.abtsoftware.co.uk/scichart"
         xmlns:s3D="http://schemas.abtsoftware.co.uk/scichart3D" xmlns:XamlRasterizer="clr-namespace:SciChart.Drawing.XamlRasterizer;assembly=SciChart.Drawing" x:Class="SciChart.Examples.Examples.SeeFeaturedApplication.ECGMonitor.ECGMonitorView"
         d:DesignHeight="400"
         d:DesignWidth="600"
         mc:Ignorable="d">

<UserControl.Resources>

    <Style TargetType="{x:Type s:RenderSurfaceBase}">
        <Setter Property="Effect">
            <Setter.Value>
                <DropShadowEffect BlurRadius="5"
                                  ShadowDepth="0"
                                  Color="#FFB3E8F6" />
            </Setter.Value>
        </Setter>
    </Style>

    <local:BeatToScaleConverter x:Key="BeatToScaleConverter" />
</UserControl.Resources>

<i:Interaction.Triggers>
    <i:EventTrigger EventName="Loaded">
        <i:InvokeCommandAction Command="{Binding StartCommand}" />
    </i:EventTrigger>
    <i:EventTrigger EventName="Unoaded">
        <i:InvokeCommandAction Command="{Binding StopCommand}" />
    </i:EventTrigger>
</i:Interaction.Triggers>

<Grid>

    <s:SciChartSurface RenderPriority="Low" MaxFrameRate="25" AutoRangeOnStartup="False">

        <s:SciChartSurface.RenderableSeries>
            <s:FastLineRenderableSeries DataSeries="{Binding EcgDataSeries}"
                Stroke="#FFB3E8F6"                                      
                StrokeThickness="2" />
        </s:SciChartSurface.RenderableSeries>

        <s:SciChartSurface.YAxis>
            <s:NumericAxis AxisTitle="Voltage (mV)"
                DrawMinorGridLines="True"
                MaxAutoTicks="5"
                VisibleRange="{Binding YVisibleRange, Mode=TwoWay}" />
        </s:SciChartSurface.YAxis>

        <s:SciChartSurface.XAxis>
            <s:NumericAxis AxisTitle="Seconds (s)" TextFormatting="0.000s" VisibleRange="0, 10" AutoRange="Never"/>
        </s:SciChartSurface.XAxis>

        <s:SciChartSurface.RenderSurface>
            <s3D:Direct3D10RenderSurface/>
        </s:SciChartSurface.RenderSurface>

    </s:SciChartSurface>

    <StackPanel Margin="30,30" Orientation="Horizontal">
        <StackPanel.Effect>
            <DropShadowEffect BlurRadius="5"
                ShadowDepth="0"
                Color="#FFB3E8F6" />
        </StackPanel.Effect>

        <Grid HorizontalAlignment="Left" VerticalAlignment="Top">
            <Canvas x:Name="layer1"
                Width="20"
                Height="20"
                Margin="12,34,10,0">
                <Canvas.RenderTransform>
                    <ScaleTransform CenterX="-6"
                        CenterY="-6"
                        ScaleX="{Binding IsBeat, Converter={StaticResource BeatToScaleConverter}}"
                        ScaleY="{Binding IsBeat, Converter={StaticResource BeatToScaleConverter}}" />
                </Canvas.RenderTransform>
                <Path Data="m 0 0 c -4 -4 -8.866933 -10.79431 -10 -15 0 0 0 -5 5 -5 5 0 5 5 5 5 0 0 0 -5 5 -5 5 0 5.242535 4.02986 5 5 -1 4 -6 11 -10 15 z" Fill="#FFB0E6F4" />
            </Canvas>
        </Grid>

        <TextBlock HorizontalAlignment="Left"
            VerticalAlignment="Top"
            FontFamily="ArialBlack"
            FontSize="36"
            FontWeight="Bold"
            Foreground="#FFB0E6F4"
            Text="{Binding HeartRate}" />
        <TextBlock HorizontalAlignment="Left"
            VerticalAlignment="Top"
            FontFamily="ArialBlack"
            FontSize="36"
            FontWeight="Bold"
            Foreground="#FFB0E6F4"
            Text="BPM" />

    </StackPanel>

    <TextBlock Margin="5"
        HorizontalAlignment="Left"
        VerticalAlignment="Bottom"
        FontSize="9"
        FontStyle="Italic"
        Foreground="#FFB0E6F4"/>

</Grid>

I’ve attached pictures of what my program currently does. I have also attached a file with sample data in case anybody wants to test the program. I have also tried building in release mode and that doesn’t help, either.

The only issue is the delay in plotting of real-time data. Otherwise, the graphing and rendering is really smooth. As I am a complete beginner to this, can somebody help with me with what I might have done wrong?

Thanks a ton,

Jaivignesh Jayakumar

Attachments
Images
  • You must to post comments
Best Answer
1
1

Hi Jaivignesh,

Is the lag or delay coming from SciChart? Or the timer? It sounds like the timer to me.

Timers in C# are notoriously bad for keeping time. I would suggest analysing why the lag occurs and if it is the timer taking another approach such as the one in the above article.

Best regards,
Andrew

  • You must to post comments
1
0

Hi Andrew,

Thanks a ton for your insight. I fixed the issue using A simple Multimedia Timer, and ran my program on release mode. That fixed up the bug.

Thanks,

Jaivignesh Jayakumar

  • You must to post comments
0
0

UPDATE –

In the ECG Monitor demo, on a sample run out of the box, there is an inherent lag in the x-axis values (time). When compared with an external stopwatch, there is an increasing delay in plotting of the voltage. At (t=50) seconds in the program (x-axis), my stopwatch shows 65 seconds elapsed. Is there any way to work around this issue?

As my program is to be used in real-time medical diagnosis, I can’t have any delay in it.

Thanks,

Jaivignesh Jayakumar

  • You must to post comments
0
0

Hi Andrew,

If the timer is the one at fault, is it the same reason for the delay in your ECG Monitor example too?

Thanks,

Jaivignesh Jayakumar

  • Andrew
    I would guess yes. Remember our examples are designed to showcase the SciChart API, not to keep perfect time – that’s not our goal! If the timer is drifting then you just need to use another technique to keep or mark time.
  • You must to post comments
Showing 4 results
Your Answer

Please first to submit.