Back to Devexpress

How to: Create a Real-Time Chart

windowsforms-401813-controls-and-libraries-chart-control-examples-creating-charts-providing-data-how-to-create-a-real-time-chart.md

latest14.0 KB
Original Source

How to: Create a Real-Time Chart

  • Jul 02, 2024
  • 6 minutes to read

This topic explains how to create a chart and add new points to its data source in real time.

Run Demo

The chart processes points that are within its viewport. In the following examples, points that are beyond the viewport are removed from the data source - starting from the beginning of the collection.

Use a Single Thread to Create a Real-Time Chart

The following code generates a new data point that is added to the chart each time the Timer.Tick event occurs (every 100 milliseconds). The example uses an ObservableCollection as the data source for a series. ObservableCollection notifies the chart about new items, and the chart is rendered again.

View Example: How to: Create a Real-Time Chart

csharp
using DevExpress.Utils;
using DevExpress.XtraCharts;
using System;
using System.Collections.ObjectModel;
using System.Windows.Forms;

namespace RealTimeChartUpdates {
    public partial class Form1 : Form {
        const int ViewportPointCount = 100;
        ObservableCollection<DataPoint> dataPoints = new ObservableCollection<DataPoint>();
        public Form1() { InitializeComponent(); }

        void Form1_Load(object sender, EventArgs e) {
            chartControl1.Titles.Add(new ChartTitle { Text = "Real-Time Charting" });

            Series series = new Series();
            series.ChangeView(ViewType.Line);
            series.DataSource = dataPoints;
            series.DataSourceSorted = true;
            series.ArgumentDataMember = "Argument";
            series.ValueDataMembers.AddRange("Value");
            chartControl1.Series.Add(series);

            LineSeriesView seriesView = (LineSeriesView)series.View;
            seriesView.LastPoint.LabelDisplayMode = SidePointDisplayMode.DiagramEdge;
            seriesView.LastPoint.Label.TextPattern = "{V:f2}";

            XYDiagram diagram = (XYDiagram)chartControl1.Diagram;
            diagram.AxisX.DateTimeScaleOptions.ScaleMode = ScaleMode.Continuous;
            diagram.AxisX.Label.ResolveOverlappingOptions.AllowRotate = false;
            diagram.AxisX.Label.ResolveOverlappingOptions.AllowStagger = false;
            diagram.AxisX.WholeRange.SideMarginsValue = 0;
            diagram.DependentAxesYRange = DefaultBoolean.True;
            diagram.AxisY.WholeRange.AlwaysShowZeroLevel = false;

            System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
            timer.Interval = 100;
            timer.Start();
            timer.Tick += Timer_Tick;
        }
        int counter = 0;
        void Timer_Tick(object sender, EventArgs e) {
            dataPoints.Add(new DataPoint(DateTime.Now, GenerateValue(counter++)));
            if (dataPoints.Count > ViewportPointCount)
                dataPoints.RemoveAt(0);
        }
        double GenerateValue(double x) { 
            return Math.Sin(x) * 3 + x / 2 + 5; 
        }
    }
    public class DataPoint {
        public DateTime Argument { get; set; }
        public double Value { get; set; }
        public DataPoint(DateTime argument, double value) {
            Argument = argument;
            Value = value;
        }
    }
}
vb
Imports DevExpress.Utils
Imports DevExpress.XtraCharts
Imports System
Imports System.Collections.ObjectModel
Imports System.Windows.Forms

Namespace RealTimeChartUpdates
    Public Partial Class Form1
        Inherits Form

        Const ViewportPointCount = 100
        Private dataPoints As ObservableCollection(Of DataPoint) = New ObservableCollection(Of DataPoint)()

        Public Sub New()
            Me.InitializeComponent()
        End Sub

        Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs)
            Me.chartControl1.Titles.Add(New ChartTitle With {
                .Text = "Real-Time Charting"
            })
            Dim series As Series = New Series()
            series.ChangeView(ViewType.Line)
            series.DataSource = dataPoints
            series.DataSourceSorted = True
            series.ArgumentDataMember = "Argument"
            series.ValueDataMembers.AddRange("Value")
            Me.chartControl1.Series.Add(series)
            Dim seriesView = CType(series.View, LineSeriesView)
            seriesView.LastPoint.LabelDisplayMode = SidePointDisplayMode.DiagramEdge
            seriesView.LastPoint.Label.TextPattern = "{V:f2}"
            Dim diagram = CType(Me.chartControl1.Diagram, XYDiagram)
            diagram.AxisX.DateTimeScaleOptions.ScaleMode = ScaleMode.Continuous
            diagram.AxisX.Label.ResolveOverlappingOptions.AllowRotate = False
            diagram.AxisX.Label.ResolveOverlappingOptions.AllowStagger = False
            diagram.AxisX.WholeRange.SideMarginsValue = 0
            diagram.DependentAxesYRange = DefaultBoolean.True
            diagram.AxisY.WholeRange.AlwaysShowZeroLevel = False
            Dim timer As Timer = New Timer()
            timer.Interval = 100
            timer.Start()
            AddHandler timer.Tick, AddressOf Timer_Tick
        End Sub

        Private counter = 0

        Private Sub Timer_Tick(ByVal sender As Object, ByVal e As EventArgs)
            dataPoints.Add(New DataPoint(Date.Now, GenerateValue(Math.Min(Threading.Interlocked.Increment(counter), counter - 1))))
            If dataPoints.Count > ViewportPointCount Then dataPoints.RemoveAt(0)
        End Sub

        Private Function GenerateValue(ByVal x As Double) As Double
            Return Math.Sin(x) * 3 + x / 2 + 5
        End Function
    End Class

    Public Class DataPoint
        Public Property Argument As Date
        Public Property Value As Double

        Public Sub New(ByVal argument As Date, ByVal value As Double)
            Me.Argument = argument
            Me.Value = value
        End Sub
    End Class
End Namespace

Collect Data in a Separate Thread

The following example uses a separate thread to accumulate data points. A new batch of data points is generated every 15 milliseconds. The chart fetches a new portion of points to visualize at a rate of ten times per second.

View Example: How to: Create a Real-Time Chart and Collect Data in a Separate Thread

csharp
using DevExpress.Utils;
using DevExpress.XtraCharts;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;

namespace RealTimeChartUpdates {
    public partial class Form1 : Form {
        const int ViewportPointCount = 5000;
        int counter = 0;
        Thread dataAcquisitionThread;
        bool isEnabled = true;
        int lastRawDataIndex = 0;
        List<DataPoint> rawData = new List<DataPoint>();
        ObservableCollection<DataPoint> viewportData = new ObservableCollection<DataPoint>();
        public Form1() { InitializeComponent(); }
        void Form1_Load(object sender, EventArgs e) {
            dataAcquisitionThread = new Thread(new ThreadStart(AcquireData));
            dataAcquisitionThread.Start();

            chartControl1.Titles.Add(new ChartTitle { Text = "Real-Time Charting" });

            Series series = new Series();
            series.ChangeView(ViewType.Line);
            series.DataSource = viewportData;
            series.DataSourceSorted = true;
            series.ArgumentDataMember = "Argument";
            series.ValueDataMembers.AddRange("Value");
            chartControl1.Series.Add(series);

            LineSeriesView seriesView = (LineSeriesView)series.View;
            seriesView.LastPoint.LabelDisplayMode = SidePointDisplayMode.SeriesPoint;
            seriesView.LastPoint.Label.TextPattern = "{V:f2}";

            XYDiagram diagram = (XYDiagram)chartControl1.Diagram;
            diagram.AxisX.DateTimeScaleOptions.ScaleMode = ScaleMode.Continuous;
            diagram.AxisX.Label.ResolveOverlappingOptions.AllowRotate = false;
            diagram.AxisX.Label.ResolveOverlappingOptions.AllowStagger = false;
            diagram.AxisX.VisualRange.EndSideMargin = 200;
            diagram.DependentAxesYRange = DefaultBoolean.True;
            diagram.AxisY.WholeRange.AlwaysShowZeroLevel = false;

            System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
            timer.Interval = 100;
            timer.Start();
            timer.Tick += Timer_Tick;
        }
        void AcquireData() {
            while (isEnabled) {
                Thread.Sleep(15);
                lock (rawData) {
                    for (int i = 0; i < 50; i++)
                        rawData.Add(new DataPoint(DateTime.Now, GenerateValue(counter++)));
                }
            }
        }
        double GenerateValue(double x) { return Math.Sin(x / 1000.0) * 3 * x + x / 2 + 5; }
        void Timer_Tick(object sender, EventArgs e) {
            lock (rawData) {
                for (int i = Math.Max(lastRawDataIndex, rawData.Count - ViewportPointCount); i < rawData.Count; i++)
                    viewportData.Add(rawData[i]);
                lastRawDataIndex = rawData.Count;
                while (viewportData.Count > ViewportPointCount)
                    viewportData.RemoveAt(0);
            }
        }
        protected override void OnClosing(CancelEventArgs e) {
            isEnabled = false;
            base.OnClosing(e);
        }
        public class DataPoint {
            public DataPoint(DateTime argument, double value) {
                Argument = argument;
                Value = value;
            }
            public DateTime Argument { get; set; }
            public double Value { get; set; }
        }
    }
}
vb
Imports DevExpress.Utils
Imports DevExpress.XtraCharts
Imports System
Imports System.Collections.Generic
Imports System.Collections.ObjectModel
Imports System.ComponentModel
Imports System.Threading
Imports System.Windows.Forms

Namespace RealTimeChartUpdates
    Public Partial Class Form1
        Inherits Form

        Const ViewportPointCount = 5000
        Private counter = 0
        Private dataAcquisitionThread As Thread
        Private isEnabled = True
        Private lastRawDataIndex = 0
        Private rawData As List(Of DataPoint) = New List(Of DataPoint)()
        Private viewportData As ObservableCollection(Of DataPoint) = New ObservableCollection(Of DataPoint)()

        Public Sub New()
            Me.InitializeComponent()
        End Sub

        Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs)
            dataAcquisitionThread = New Thread(New ThreadStart(AddressOf AcquireData))
            dataAcquisitionThread.Start()
            Me.chartControl1.Titles.Add(New ChartTitle With {
                .Text = "Real-Time Charting"
            })
            Dim series As Series = New Series()
            series.ChangeView(ViewType.Line)
            series.DataSource = viewportData
            series.DataSourceSorted = True
            series.ArgumentDataMember = "Argument"
            series.ValueDataMembers.AddRange("Value")
            Me.chartControl1.Series.Add(series)
            Dim seriesView = CType(series.View, LineSeriesView)
            seriesView.LastPoint.LabelDisplayMode = SidePointDisplayMode.SeriesPoint
            seriesView.LastPoint.Label.TextPattern = "{V:f2}"
            Dim diagram = CType(Me.chartControl1.Diagram, XYDiagram)
            diagram.AxisX.DateTimeScaleOptions.ScaleMode = ScaleMode.Continuous
            diagram.AxisX.Label.ResolveOverlappingOptions.AllowRotate = False
            diagram.AxisX.Label.ResolveOverlappingOptions.AllowStagger = False
            diagram.AxisX.VisualRange.EndSideMargin = 200
            diagram.DependentAxesYRange = DefaultBoolean.True
            diagram.AxisY.WholeRange.AlwaysShowZeroLevel = False
            Dim timer As System.Windows.Forms.Timer = New System.Windows.Forms.Timer()
            timer.Interval = 100
            timer.Start()
            AddHandler timer.Tick, AddressOf Timer_Tick
        End Sub

        Private Sub AcquireData()
            While isEnabled
                Thread.Sleep(15)
                SyncLock rawData
                    For i = 0 To 50 - 1
                        rawData.Add(New DataPoint(Date.Now, GenerateValue(Math.Min(Interlocked.Increment(counter), counter - 1))))
                    Next
                End SyncLock
            End While
        End Sub
        Private Function GenerateValue(ByVal x As Double) As Double
            Return Math.Sin(x / 1000.0) * 3 * x + x / 2 + 5
        End Function
        Private Sub Timer_Tick(ByVal sender As Object, ByVal e As EventArgs)
            SyncLock rawData

                For i = Math.Max(lastRawDataIndex, rawData.Count - ViewportPointCount) To rawData.Count - 1
                    viewportData.Add(rawData(i))
                Next

                lastRawDataIndex = rawData.Count

                While ViewportData.Count > ViewportPointCount
                    viewportData.RemoveAt(0)
                End While
            End SyncLock
        End Sub
        Protected Overrides Sub OnClosing(ByVal e As CancelEventArgs)
            isEnabled = False
            MyBase.OnClosing(e)
        End Sub
        Public Class DataPoint
            Public Sub New(ByVal argument As Date, ByVal value As Double)
                Me.Argument = argument
                Me.Value = value
            End Sub

            Public Property Argument As Date
            Public Property Value As Double
        End Class
    End Class
End Namespace

See Also

Best Practices: Display Large Data