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.

0 votes
628 views

Hey There.

I am new to sciChart and i am in ahurry to develop a realtime ECG App which takes data from Bluetooth. Ignore the harware part. Can anyone kindly prove me source code of realtime ecg monitoring or realtime linechart.

Thanx in advance

0 votes
1k views

I’ve been trying a few ways to get this to work now and I’m still struggling to get a catch-all solution.

My requirements are:
– self-scrolling real-time data series with a fixed-time range (regardless of the number of points)
– y axis should support either fixed or automatic visual range
– support rubber band zoom (and ideally mousewheel too) on both axes

The key problem is that the stock ticker example doesn’t have a fixed time width. The points are added to the chart, and the axis expands until it reaches capacity. It’s also relatively easy to use FifoCapacity (adding a lot of NaN points), but FifoCapacity works on number of points, which isn’t great when you have irregular updates. I’m happy with the chart not scrolling until a new update is received, but the range of the chart must be fixed (say, five minutes), regardless of the number of visible points on the chart.

My current solution involves deriving from XyDataSeries to duplicate the FifoCapacity behaviour for a fixed time:

   public class RealTimeXyDataSeries<T> : XyDataSeries<DateTime, T>
            where T : IComparable
{
    public TimeSpan _limit;

    public TimeSpan Limit
    {
        get { return _limit; }
        set
        {
            _limit = value;
            Update();
        }
    }

    public RealTimeXyDataSeries(TimeSpan limit)
    {
        Limit = limit;
    }

    public override void Append(DateTime x, T y)
    {
        base.Append(x, y);

        Update();
    }

    public void Update()
    {
        if ((DateTime)XMax == DateTime.MinValue) return;

        var c = 0;

        while (XValues[c] < (DateTime) XMax - Limit) c++;

        if (c <= 0) return;

        RemoveRange(0, c);
    }
}

That takes care of removing points, but doesn’t deal with the range issue. To deal with that, I have some code in my codebehind to set the XAxis VisibleRange on every update, which feels a bit nasty. I wanted to create a RealTimeDateTimeAxis, but none of the relevant functions are override/virtual.

I also took the implementation of RubberBandXyZoomModifierEx from the knowledge base, with some modifications to deal with Y-Axis zoom/reset based on it’s AutoRange setting. However, this involves setting the Y-Axis AutoRange state back to a saved value, and I keep seeing MouseModifierUp events after my DoubleClick events, which resets AutoRange back to Never.

It feels like I’m doing a lot of work in a lot of places to get this to work, and it’s resulting in complex code and buggy behaviours; I’m not comfortable handing this over to another engineer in the current state.

Is there a better way to do this?

  • Rick C asked 2 years ago
  • last active 2 years ago
1 vote
1k views

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

1 vote
5k views

Hi,

I have multiple dataseries of around 20,000 points each, and the calculation of each point originates from one of three sources. I would like to show, for each point, a different data point marker depending on the origin of the point. Each point marker should also show some meta data about the calculation origin in a tooltip when it is moused over.

I have been able to achieve this effect using the Annotations API, as shown here: http://s4.postimg.org/otgc4yk3x/markers.png but chart performance grinds to a halt with an AnnotationCollection of up to 100,000 annotations attached to the surface.

Can I show this information in another way that will be performant, taking advantage of virtualisation, efficient algorithms on sorted data etc.? I was thinking that I may be able to add the point origins as metadata to the XyDataSeries, benefit from the performance optimisations that come with a SciChart series, and show the metadata in DataPointMarkers with tooltips? Could this work? Can I also have a differently shaped datapoint marker for each point depending on the metadata?

Thanks đŸ™‚

Showing 4 results
This template supports the sidebar's widgets. Add one or use Full Width layout.