Click here to Skip to main content
15,039,319 members
Articles / Desktop Programming / WPF
Article
Posted 6 Jan 2017

Tagged as

Stats

23K views
1.9K downloads
15 bookmarked

WPF Application with Real-time Data in OxyPlot Charts

Rate me:
Please Sign up or sign in to vote.
5.00/5 (5 votes)
6 Jan 2017CPOL3 min read
The is a WPF application that shows (emulated) real-time data in charts.
The solution presented in this article contains one WPF application project. The main part of the application is chart repository - the object that receives data from services, parses and provides it to consumers.

Image 1

Introduction

The post is devoted to the WPF application that shows (emulated) real-time data in charts. It could be used as base for monitoring application, when data service provide real data like, for example, number of http requests or CPU temperature. OxyPlot library is used for chart controls, as it is lightweight and easy to use.

Features

Application demonstrates the following features:

  1. Data service provides (emulated) real-time data
  2. Chart repository parses data and provides latest data
  3. Data is shown in line and column charts
  4. Using of dependency container

Background

Solution uses C#6, .NET 4.6.1, WPF with MVVM pattern, NuGet packages Unity and Ikc5.TypeLibrary, and OxyPlot chart control. OxyPlot is added to project by NuGet packages OxyPlot core library (PCL) 1.0.0 and OxyPlot for WPF 1.0.0.

Solution

The solution contains one WPF application project. The main part of the application is chart repository - the object that receives data from services, parses and provides it to consumers. Data service emulates data, but in real-world example, it reads data from external source, and puts it to repository. Data service is controlled by the application, and there are several instances that could be executed. User controls consume data from repository and update charts.

Chart Repository

ChartRepository class implements IChartRepository interface:

C#
public interface IChartRepository : INotifyPropertyChanged
{
  IReadOnlyList<int> LineCountList { get; }
  IReadOnlyList<int> ColumnCountList { get; }

  void AddLineCount(int newValue);
  void AddColumnCount(int index, int newValue);
}

Properties are used by consumers, that in this application are view models for user controls. LineCountList provides one-dimensional series as values for some process. Repository keeps not great than 100 values. Data services use AddLineCount to add new value. ColumnCountList provides two-dimensional data like the set of pairs (index, value), and could be used as distribution of process values. Data services use AddColumnCount to add pair (index, new value).

The instance of repository should be singleton. Lifetime of this object is controlled by DI container. In addition, base interface INotifyPropertyChanged allows inform consumers that properties were changed and could be used in WPF data binding mechanism.

WPF Application

Wpf application is done in MVVM pattern with one main window. Main window contains two user controls: ColumnChartView and LineChartView. Relatively, main window view model contains two properties with view models for child controls:

C#
public interface IMainWindowViewModel
{
  ILineChartViewModel LineChartViewModel { get; }
  IColumnChartViewModel ColumnChartViewModel { get; }
}

and:

XML
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>

    <ContentControl
        Grid.Column="0">
        <views:ColumnChartView
            DataContext="{Binding ColumnChartViewModel}"/>
    </ContentControl>

    <ContentControl
        Grid.Column="1">
        <views:LineChartView
            DataContext="{Binding LineChartViewModel}"/>
    </ContentControl>

</Grid>

ColumnChartViewModel gets chart repository as constructor parameter and transform ColumnCountList value to the list of Tuple objects:

C#
public IReadOnlyList<Tuple<string, int>> CountList =>
  _chartRepository.ColumnCountList.
  Select((value, index) => new Tuple<string, int>(index.ToString("D"), value)).
  ToList();

Now it could be bound to OxyPlot chart controls:

XML
<oxy:Plot
    x:Name="CountPlot"
    LegendPlacement="Outside"
    LegendPosition="TopCenter"
    LegendOrientation="Horizontal"
    LegendBorderThickness="0"
    PlotAreaBorderThickness="0"
    IsEnabled="False"
    Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}">

    <oxy:Plot.Axes>
        <!-- axes don't use binding-->
    </oxy:Plot.Axes>

    <oxy:Plot.Series>
        <oxy:ColumnSeries
            x:Name="CountSeries"
            Title="Counts"
            ItemsSource="{Binding CountList}"
            Color="Blue"
            StrokeThickness="1"
            ValueField="Item2"/>

    </oxy:Plot.Series>
</oxy:Plot>

Similarly, LineChartViewModel gets chart repository as constructor parameter and transform LineCountList value to the list of DataPoint objects:

C#
public IReadOnlyList<DataPoint> CountList =>
  _chartRepository.LineCountList.Select((value, index) => new DataPoint(index, value)).ToList();

and:

XML
<oxy:Plot
    x:Name="CellCountPlot"
    LegendPlacement="Outside"
    LegendPosition="TopCenter"
    LegendOrientation="Horizontal"
    LegendBorderThickness="0"
    PlotAreaBorderThickness="0"
    IsEnabled="False"
    Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}">

    <oxy:Plot.Axes>
        <!-- axes don't use binding-->
    </oxy:Plot.Axes>

    <oxy:Plot.Series>
        <oxy:LineSeries
            x:Name="TotalSeries"
            Title="Count"
            ItemsSource="{Binding CountList}"
            DataFieldX="X"
            DataFieldY="Y"
            Color="Blue"
            StrokeThickness="1"/>

    </oxy:Plot.Series>
</oxy:Plot>

Data Service

Data service could be described by IService interface. It's quite simple, and allows to start and to stop service execution:

C#
public interface IService
{
  /// <summary>
  /// Start service.
  /// </summary>
  void OnStart();

  /// <summary>
  /// Stop service, cleanup data.
  /// </summary>
  void OnStop();
}

Service is started and stopped in application App class:

C#
protected override void OnStartup(StartupEventArgs e)
{
  base.OnStartup(e);

  // container and other initialization
  // ...

  _chartService = container.Resolve<IService>();
  _chartService.OnStart();
}

protected override void OnExit(ExitEventArgs e)
{
  _chartService.OnStop();
  base.OnExit(e);
}

Data service is implemented by ChartService class. It inherits IService interface, and uses DispatcherTimer object and, when it ticks, emulates data by using random generators:

C#
private readonly IChartRepository _chartRepository;

public ChartService(IChartRepository chartRepository)
{
  chartRepository.ThrowIfNull(nameof(chartRepository));
  _chartRepository = chartRepository;

  _countTimer = new DispatcherTimer
  {
    Interval = TimeSpan.FromMilliseconds(250),
    IsEnabled = false
  };
  _countTimer.Tick += CountTimerTick;
}

/// <summary>
/// Method emulates new data.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void CountTimerTick(object sender, EventArgs e)
{
  var value = _countRandom.Next(150);
  _chartRepository.AddLineCount(value);

  var index = _indexRandom.Next(50);
  value = _countRandom.Next(100);
  _chartRepository.AddColumnCount(index, value);

  _countTimer.Start();
}

As data is emulated in timer' tick method, start and stop methods are correspond to start/stop methods of the timer:

C#
/// <summary>
/// Start process.
/// </summary>
public void OnStart()
{
  _countTimer.Start();
}

/// <summary>
/// Stop process, cleanup data.
/// </summary>
public void OnStop()
{
  _countTimer.Stop();
}

Dependency Container

Unity is used as dependency container. As was mentioned above, main window view model keeps view models for child controls, and these properties are expected to be resolved via dependency injection. So, at first, we need register necessary types for view models, chart repository and service in dependency injection container. In WPF applications, the initialization of DI container is done in OnStartup method in App.xaml.cs:

C#
protected override void OnStartup(StartupEventArgs e)
{
  base.OnStartup(e);

  IUnityContainer container = new UnityContainer();

  container.RegisterType<ILogger, EmptyLogger>();
  container.RegisterType<IChartRepository, ChartRepository>
                        (new ContainerControlledLifetimeManager());
  container.RegisterType<IService, ChartService>(new ContainerControlledLifetimeManager());
  container.RegisterType<IMainWindowViewModel, MainWindowViewModel>();
  container.RegisterType<IColumnChartViewModel, ColumnChartViewModel>();
  container.RegisterType<ILineChartViewModel, LineChartViewModel>();

  var mainWindow = container.Resolve<MainWindow>();
  Application.Current.MainWindow = mainWindow;
  Application.Current.MainWindow.Show();

  // other initialization
  // ...
}

In MainWindowViewModel class, Dependency attribute is used. It informs container to resolve these properties during object's resolve process:

C#
public class MainWindowViewModel : IMainWindowViewModel
{
  [Dependency]
  public ILineChartViewModel LineChartViewModel { get; set; }

  [Dependency]
  public IColumnChartViewModel ColumnChartViewModel { get; set;  }
}

History

  • 6th January, 2017: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Illya Reznykov
Software Developer (Senior)
Ukraine Ukraine
• Have more than 25 years of the architecting, implementing, and supporting various applications from small desktop and web utilities up to full-fledged cloud SaaS systems using mainly Microsoft technology stack and implementing the best practices.
• Have significant experience in the architecting applications starting from the scratch and from the existent application (aka “legacy”) where it is required to review, refactor, optimise the codebase and data structure, migrate to new technologies, implement new features, best practices, create tests and write documentation.
• Have experience in project management, collecting business requirements, creating MVP, working with stakeholders and end users, and tasks and backlog management.
• Have hands-on experience in the setting up CI/CD pipelines, the deploying on-premise and cloud systems both in Azure and AWS, support several environments.
• As Mathematician, I interested much in the theory of automata and computer algebra.

Comments and Discussions

 
QuestionHow to render lines and columns in a single PlotModel ? Pin
Oleksandr Dodatko25-Jan-18 22:30
MemberOleksandr Dodatko25-Jan-18 22:30 
Thanks for a great article, Illya.

Could you, please, give me a hand with OxyPlot usage?
I need to render lines and columns on the same canvas like this https://i.stack.imgur.com/yrgfU.gif

"Just lines" or "Just columns" work like a charm. However, they do not seem to play nicely together.
https://github.com/oxyplot/oxyplot/issues/1187

Could you please suggest any ideas?
Thanks in advance.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.