Thought that I would throw my hat into the ring in my normal (multi-language) style but a single solution only... ;)
One advantage of this solution, besides the very small memory footprint, is that you can use one or more endless streams of data, not just a fixed array with a fixed window.
using System;
using System.Linq;
namespace RollingSD
{
class Program
{
static void Main(string[] args)
{
double[] data = { 1E30f, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
Console.WriteLine("Running");
Console.WriteLine("-------\r\n");
int count = 0;
var avg = 0.0;
var t = new Tuple<double, double, int>(0.0, 0.0, 0);
for (int i = 0; i < data.Length; i++)
{
avg = avg.Average(data[i], ref count);
Console.WriteLine($"Dataset: {{{string.Join(", ", data.Take(i + 1))}}}");
Console.WriteLine($"Results: Avg = {avg,-20} Std Dev = {data[i].StandardDeviation(ref t),-20}\r\n");
}
Console.WriteLine("\r\n\r\nWindowed");
Console.WriteLine("--------\r\n");
var window = new CircularBuffer<double>(5);
foreach (var item in data)
{
window.Add(item);
Console.WriteLine($"Dataset: {window}");
Console.WriteLine($"Results: Avg = {window.Average(),-20} Std Dev = {window.StandardDeviation(),-20}\r\n");
}
Console.WriteLine("-- Press any ket to exit --");
Console.ReadKey();
}
}
public static class HelperExtensions
{
public static double StandardDeviation(this CircularBuffer<double> data)
{
var s1 = 0.0;
var t = new Tuple<double, double, int>(0.0, 0.0, 0);
for (int i = 0; i < data.Length; i++)
s1 = data[i].StandardDeviation(ref t);
return s1;
}
public static double StandardDeviation(this double value, ref Tuple<double, double, int> t)
{
var c = t.Item3 + 1;
double m, s = m = 0.0;
if (c == 1)
m = value;
else
{
m = t.Item1 + (value - t.Item1) / c;
s = t.Item2 + (value - t.Item1) * (value - m);
}
t = new Tuple<double, double, int>(m, s, c);
return Math.Sqrt(c > 1 ? s / (c - 1) : 0);
}
public static double Average(this CircularBuffer<double> data)
{
var a1 = 0.0;
int count = 0;
for (int i = 0; i < data.Length; i++)
a1 = a1.Average(data[i], ref count);
return a1;
}
public static double Average(this double average, double value, ref int count)
=> (average * count + value) / ++count;
}
public class CircularBuffer<T>
{
T[] buffer;
int nextFree, len;
public CircularBuffer(int length)
{
buffer = new T[length];
nextFree = 0;
}
public void Add(T o)
{
buffer[nextFree] = o;
nextFree = (nextFree + 1) % buffer.Length;
if (len < buffer.Length) len++;
}
public T this[int index]
=> index >= 0 && index <= buffer.Length ? buffer[index] : default(T);
public int Length => len;
public override string ToString()
=> $"{{{string.Join(", ", ToList())}}}";
public IEnumerable<T> ToList()
{
foreach (var item in nextFree < len
? buffer.Skip(nextFree)
.Take(len - nextFree)
.Concat(buffer.Take(nextFree))
: buffer.Take(len))
yield return item;
}
}
}
Imports System.Runtime.CompilerServices
Module Module1
Sub Main()
Dim data As Double() = {1.0E+30F, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
Console.WriteLine("Running")
Console.WriteLine("-------{0}", vbCrLf)
Dim count As Integer = 0
Dim avg As Double = 0F
Dim t = New Tuple(Of Double, Double, Integer)(0.0, 0.0, 0)
For i As Integer = 0 To data.Length - 1
avg = avg.Average(data(i), count)
Console.WriteLine("Dataset: {{{0}}}", String.Join(", ", data.Take(i + 1)))
Console.WriteLine("Results: Avg = {0,-20} Std Dev = {1,-20}{2}",
avg, data(i).StandardDeviation(t), vbCrLf)
Next
Console.WriteLine("{0}{0}Windowed", vbCrLf)
Console.WriteLine("--------{0}", vbCrLf)
Dim window = New CircularBuffer(Of Double)(5)
For Each item In data
window.Add(item)
Console.WriteLine("Dataset: {0}", window)
Console.WriteLine("Results: Avg = {0,-20} Std Dev = {1,-20}{2}",
window.Average(), window.StandardDeviation(), vbCrLf)
Next
Console.WriteLine("-- Press any ket to exit --")
Console.ReadKey()
End Sub
End Module
Module HelperExtensions
<Extension>
Public Function StandardDeviation(data As CircularBuffer(Of Double)) As Double
Dim s1 = 0.0
Dim t = New Tuple(Of Double, Double, Integer)(0.0, 0.0, 0)
For i As Integer = 0 To data.Length - 1
s1 = data(i).StandardDeviation(t)
Next
Return s1
End Function
<Extension>
Public Function StandardDeviation(value As Double, ByRef t As Tuple(Of Double, Double, Integer)) As Double
Dim c = t.Item3 + 1
Dim m = 0.0
Dim s = 0.0
If c = 1 Then
m = value
Else
m = t.Item1 + (value - t.Item1) / c
s = t.Item2 + (value - t.Item1) * (value - m)
End If
t = New Tuple(Of Double, Double, Integer)(m, s, c)
Return Math.Sqrt(IIf(c > 1, s / (c - 1), 0))
End Function
<Extension>
Public Function Average(data As CircularBuffer(Of Double)) As Double
Dim a1 = 0.0
Dim count As Integer = 0
For i As Integer = 0 To data.Length - 1
a1 = a1.Average(data(i), count)
Next
Return a1
End Function
<Extension>
Public Function Average(avg As Double, value As Double, ByRef count As Integer) As Double
count += 1
Dim ret As Double = (avg * (count - 1) + value) / count
Return ret
End Function
End Module
Public Class CircularBuffer(Of T)
Private buffer As T()
Private nextFree As Integer, len As Integer
Public Sub New(length As Integer)
buffer = New T(length - 1) {}
nextFree = 0
End Sub
Public Sub Add(o As T)
buffer(nextFree) = o
nextFree = (nextFree + 1) Mod buffer.Length
If len < buffer.Length Then
len += 1
End If
End Sub
Default Public ReadOnly Property Item(index As Integer) As T
Get
Return If(index >= 0 AndAlso index <= buffer.Length, buffer(index), Nothing)
End Get
End Property
Public ReadOnly Property Length() As Integer
Get
Return len
End Get
End Property
Public Overrides Function ToString() As String
Return String.Format("{{{0}}}", String.Join(", ", ToList()))
End Function
Public Iterator Function ToList() As IEnumerable(Of T)
For Each Item As T In If(nextFree < len,
buffer.Skip(nextFree) _
.Take(len - nextFree) _
.Concat(buffer.Take(nextFree)), buffer.Take(len))
Yield Item
Next
End Function
End Class
Output:
Running
-------
Dataset: {1.00000001504747E+30}
Results: Avg = 1.00000001504747E+30 Std Dev = 0
Dataset: {1.00000001504747E+30, 1}
Results: Avg = 5.00000007523733E+29 Std Dev = 7.07106791826713E+29
Dataset: {1.00000001504747E+30, 1, 1}
Results: Avg = 3.33333338349155E+29 Std Dev = 5.77350277877284E+29
Dataset: {1.00000001504747E+30, 1, 1, 1}
Results: Avg = 2.50000003761867E+29 Std Dev = 5.00000007523733E+29
Dataset: {1.00000001504747E+30, 1, 1, 1, 1}
Results: Avg = 2.00000003009493E+29 Std Dev = 4.47213602229389E+29
Dataset: {1.00000001504747E+30, 1, 1, 1, 1, 1}
Results: Avg = 1.66666669174578E+29 Std Dev = 4.08248296606965E+29
Dataset: {1.00000001504747E+30, 1, 1, 1, 1, 1, 1}
Results: Avg = 1.42857145006781E+29 Std Dev = 3.77964478696635E+29
Dataset: {1.00000001504747E+30, 1, 1, 1, 1, 1, 1, 1}
Results: Avg = 1.25000001880933E+29 Std Dev = 3.53553395913357E+29
Dataset: {1.00000001504747E+30, 1, 1, 1, 1, 1, 1, 1, 1}
Results: Avg = 1.11111112783052E+29 Std Dev = 3.33333338349155E+29
Dataset: {1.00000001504747E+30, 1, 1, 1, 1, 1, 1, 1, 1, 1}
Results: Avg = 1.00000001504747E+29 Std Dev = 3.16227770775265E+29
Dataset: {1.00000001504747E+30, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
Results: Avg = 9.09090922770424E+28 Std Dev = 3.01511349114745E+29
Windowed
--------
Dataset: {1.00000001504747E+30}
Results: Avg = 1.00000001504747E+30 Std Dev = 0
Dataset: {1.00000001504747E+30, 1}
Results: Avg = 5.00000007523733E+29 Std Dev = 7.07106791826713E+29
Dataset: {1.00000001504747E+30, 1, 1}
Results: Avg = 3.33333338349155E+29 Std Dev = 5.77350277877284E+29
Dataset: {1.00000001504747E+30, 1, 1, 1}
Results: Avg = 2.50000003761867E+29 Std Dev = 5.00000007523733E+29
Dataset: {1.00000001504747E+30,1,1,1,1}
Results: Avg = 2.00000003009493E+29 Std Dev = 4.47213602229389E+29
Dataset: {1,1,1,1,1}
Results: Avg = 1 Std Dev = 0
Dataset: {1,1,1,1,1}
Results: Avg = 1 Std Dev = 0
Dataset: {1,1,1,1,1}
Results: Avg = 1 Std Dev = 0
Dataset: {1,1,1,1,1}
Results: Avg = 1 Std Dev = 0
Dataset: {1,1,1,1,1}
Results: Avg = 1 Std Dev = 0
Dataset: {1,1,1,1,1}
Results: Avg = 1 Std Dev = 0
-- Press any ket to exit --
Passes
Harold's test[
^] from The Lounge. :)
UPDATE: I thought that I would add a live visualization of the formula using the above code. Here is a
screenshot[
^] of the app. Data fed in every 1/2 second and runs until closed.
To help with the visualzations, I have used the excellent free charting library
Live Charts[
^].
1. Xaml page
<Window
x:Class="WpfRollingSD.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
xmlns:vm="clr-namespace:WpfRollingSD.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" WindowStartupLocation="CenterScreen"
Title="Coding Challenge: Windows Std Dev & Average" Height="660" Width="600">
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
<Grid Margin="15" MaxHeight="600">
<Grid.Effect>
<DropShadowEffect BlurRadius="15" Direction="-90"
RenderingBias="Quality" Opacity=".2"
ShadowDepth="1"/>
</Grid.Effect>
<Grid.OpacityMask>
<VisualBrush Visual="{Binding ElementName=Border1}" />
</Grid.OpacityMask>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="0.60*"/>
<RowDefinition Height="0.4*"/>
</Grid.RowDefinitions>
<Grid.Resources>
<Style x:Key="CleanSeparator" TargetType="lvc:Separator">
<Setter Property="IsEnabled" Value="False"/>
</Style>
<Style x:Key="ValueStyle" TargetType="lvc:LineSeries">
<Setter Property="Stroke" Value="Red"/>
<Setter Property="Fill" Value="Transparent"/>
<Setter Property="StrokeDashArray" Value="2"/>
<Setter Property="PointGeometrySize" Value="6"/>
<Setter Property="PointGeometry"
Value="{x:Static lvc:DefaultGeometries.Diamond}"/>
</Style>
<Style x:Key="AverageStyle" TargetType="lvc:LineSeries">
<Setter Property="Stroke" Value="#FF4095FC"/>
<Setter Property="Fill" Value="Transparent"/>
<Setter Property="PointGeometrySize" Value="0"/>
<Setter Property="LineSmoothness" Value="0"/>
</Style>
<Style x:Key="StdDevStyle" TargetType="lvc:LineSeries">
<Setter Property="Stroke" Value="#FF004A26"/>
<Setter Property="Fill" Value="Transparent"/>
<Setter Property="PointGeometrySize" Value="0"/>
<Setter Property="LineSmoothness" Value="0"/>
</Style>
</Grid.Resources>
<Border x:Name="Border1" Grid.Row="0" Grid.RowSpan="4" CornerRadius="5" Background="White"/>
<Border Grid.Row="0" Grid.RowSpan="3"/>
<TextBlock Grid.Row="0" TextAlignment="Center" Padding="10, 10, 0, 5" FontSize="18">
Windowed Standard Deviation & Average
</TextBlock>
<lvc:CartesianChart Grid.Row="2" Margin="10 0"
BorderBrush="LightGray" BorderThickness="0 0 0 1"
Hoverable="False"
DataTooltip="{x:Null}">
<lvc:CartesianChart.AxisY>
<lvc:Axis Foreground="Red" Title="Value">
<lvc:Axis.Separator>
<lvc:Separator Style="{StaticResource CleanSeparator}"/>
</lvc:Axis.Separator>
</lvc:Axis>
<lvc:Axis Foreground="#FF4095FC" Title="Average" Position="RightTop">
<lvc:Axis.Separator>
<lvc:Separator Style="{StaticResource CleanSeparator}"/>
</lvc:Axis.Separator>
</lvc:Axis>
<lvc:Axis Foreground="#FF004A26" Title="Standard Deviation"
Position="RightTop">
<lvc:Axis.Separator>
<lvc:Separator Style="{StaticResource CleanSeparator}"/>
</lvc:Axis.Separator>
</lvc:Axis>
</lvc:CartesianChart.AxisY>
<lvc:CartesianChart.AxisX>
<lvc:Axis MinValue="2" ShowLabels="False" IsEnabled="False"/>
</lvc:CartesianChart.AxisX>
<lvc:CartesianChart.Series>
<lvc:LineSeries Values="{Binding ValueSeries[0].Values}"
Style="{StaticResource ValueStyle}" ScalesYAt="0"/>
<lvc:LineSeries Values="{Binding ValueSeries[1].Values}"
Style="{StaticResource AverageStyle}" ScalesYAt="1"/>
<lvc:LineSeries Values="{Binding ValueSeries[2].Values}"
Style="{StaticResource StdDevStyle}" ScalesYAt="2"/>
</lvc:CartesianChart.Series>
</lvc:CartesianChart>
<Grid Grid.Row="3" HorizontalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.Resources>
<Style x:Key="HeaderStyle" TargetType="TextBlock">
<Setter Property="TextAlignment" Value="Left"/>
<Setter Property="Margin" Value="0 0 0 10"/>
<Setter Property="FontWeight" Value="Bold"/>
</Style>
<Style TargetType="StackPanel">
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="Margin" Value="10"/>
</Style>
</Grid.Resources>
<StackPanel>
<TextBlock Style="{StaticResource HeaderStyle}">
Values
</TextBlock>
<ItemsControl ItemsSource="{Binding ValueSeries[0].Values}"
DisplayMemberPath="Value"/>
</StackPanel>
<StackPanel Grid.Column="1">
<TextBlock Style="{StaticResource HeaderStyle}">
Averages
</TextBlock>
<ItemsControl ItemsSource="{Binding ValueSeries[1].Values}"
DisplayMemberPath="Value"/>
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Style="{StaticResource HeaderStyle}">
Std Devs
</TextBlock>
<ItemsControl ItemsSource="{Binding ValueSeries[2].Values}"
DisplayMemberPath="Value"/>
</StackPanel>
</Grid>
</Grid>
</Window>
2. ViewModel:
using LiveCharts;
using LiveCharts.Defaults;
using LiveCharts.Wpf;
using System;
using System.Linq;
using System.Windows.Threading;
namespace WpfRollingSD.ViewModels
{
public class MainViewModel
{
public MainViewModel()
{
ValueSeries = new SeriesCollection()
{
new LineSeries { Values = new ChartValues<ObservableValue>() },
new LineSeries { Values = new ChartValues<ObservableValue>() },
new LineSeries { Values = new ChartValues<ObservableValue>() }
};
timer.Interval = new TimeSpan(0, 0, 0, 0, 500);
timer.Tick += Update;
timer.Start();
}
public SeriesCollection ValueSeries { get; set; }
private DispatcherTimer timer = new DispatcherTimer();
private CircularBuffer<double> window = new CircularBuffer<double>(5);
private void Update(object sender, EventArgs e)
{
var r = new Random();
var value = r.Next(-10, 10);
window.Add(value);
ValueSeries[0].Values.Add(new ObservableValue(value));
ValueSeries[1].Values.Add(new ObservableValue(window.Average()));
ValueSeries[2].Values.Add(new ObservableValue(window.StandardDeviation()));
if (ValueSeries[0].Values.Count > 12)
{
ValueSeries[0].Values.RemoveAt(0);
ValueSeries[1].Values.RemoveAt(0);
ValueSeries[2].Values.RemoveAt(0);
}
}
}
}
Imports System.Runtime.CompilerServices
Imports System.Windows.Threading
Imports LiveCharts
Imports LiveCharts.Defaults
Imports LiveCharts.Wpf
Public Class MainViewModel
Public Sub New()
ValueSeries = New SeriesCollection() From
{
New LineSeries() With {.Values = New ChartValues(Of ObservableValue)()},
New LineSeries() With {.Values = New ChartValues(Of ObservableValue)()},
New LineSeries() With {.Values = New ChartValues(Of ObservableValue)()}
}
timer.Interval = New TimeSpan(0, 0, 0, 0, 500)
AddHandler timer.Tick, AddressOf Update
timer.Start()
End Sub
Public Property ValueSeries() As SeriesCollection
Private timer As New DispatcherTimer()
Private window As New CircularBuffer(Of Double)(5)
Private Sub Update(sender As Object, e As EventArgs)
Dim r = New Random()
Dim value = r.[Next](-10, 10)
window.Add(value)
ValueSeries(0).Values.Add(New ObservableValue(value))
ValueSeries(1).Values.Add(New ObservableValue(window.Average()))
ValueSeries(2).Values.Add(New ObservableValue(window.StandardDeviation()))
If ValueSeries(0).Values.Count > 12 Then
ValueSeries(0).Values.RemoveAt(0)
ValueSeries(1).Values.RemoveAt(0)
ValueSeries(2).Values.RemoveAt(0)
End If
End Sub
End Class
You can download and try out the app:
*
WPF C# Version[
^]
*
WPF VB Version[
^]
UPDATE #2: Not everyone does WPF. So here are WinForms Live Data versions that works just like the WPF versions above.
using System;
using System.Linq;
using System.Windows.Media;
using System.Windows.Forms;
using LiveCharts;
using LiveCharts.Wpf;
using System.Collections.Generic;
using LiveCharts.Defaults;
namespace WfRollingSD
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
InitChart();
SetBindings();
Timer = new Timer { Interval = 500 };
Timer.Tick += Update;
Timer.Start();
}
private void InitChart()
{
cartesianChart1.AxisY.Add(new Axis
{
Foreground = Brushes.Red,
Title = "Value"
});
cartesianChart1.Series.Add(new LineSeries
{
Stroke = Brushes.Red,
Fill = Brushes.Transparent,
StrokeDashArray = new DoubleCollection { 2 },
PointGeometrySize = 6,
PointGeometry = DefaultGeometries.Diamond,
ScalesYAt = 0,
Values = new ChartValues<ObservableValue>()
});
cartesianChart1.AxisY.Add(new Axis
{
Foreground = new SolidColorBrush(Color.FromArgb(0xFF, 0x40, 0x95, 0xFC)),
Title = "Average",
Position = AxisPosition.RightTop,
Separator = new Separator { IsEnabled = false }
});
cartesianChart1.Series.Add(new LineSeries
{
Stroke = new SolidColorBrush(Color.FromArgb(0xFF, 0x40, 0x95, 0xFC)),
Fill = Brushes.Transparent,
PointGeometrySize = 0,
LineSmoothness = 0,
ScalesYAt = 1,
Values = new ChartValues<ObservableValue>()
});
cartesianChart1.AxisY.Add(new Axis
{
Foreground = new SolidColorBrush(Color.FromArgb(0xFF, 0x00, 0x4A, 0x26)),
Title = "Standard Deviation",
Position = AxisPosition.RightTop,
Separator = new Separator { IsEnabled = false }
});
cartesianChart1.Series.Add(new LineSeries
{
Stroke = new SolidColorBrush(Color.FromArgb(0xFF, 0x00, 0x4A, 0x26)),
Fill = Brushes.Transparent,
PointGeometrySize = 0,
LineSmoothness = 0,
ScalesYAt = 2,
Values = new ChartValues<ObservableValue>()
});
cartesianChart1.AxisX.Add(new Axis
{
ShowLabels = false,
IsEnabled = false
});
}
private void RefreshBindings()
{
if (lbValues.InvokeRequired)
{
lbValues.Invoke((Action)(() => RefreshBindings()));
return;
}
SetBindings();
RefreshUI();
}
private BindingSource valueBinding = new BindingSource();
private BindingSource avgBinding = new BindingSource();
private BindingSource stdDevBinding = new BindingSource();
private void SetBindings()
{
lbValues.Bind<int>(valueBinding, cartesianChart1.Series[0].Values);
lbAverages.Bind<double>(avgBinding, cartesianChart1.Series[1].Values);
lbStdDevs.Bind<double>(stdDevBinding, cartesianChart1.Series[2].Values);
}
private void RefreshUI()
{
lbValues.Refresh();
lbAverages.Refresh();
lbStdDevs.Refresh();
}
private Timer Timer;
private Random r = new Random();
private CircularBuffer<double> window = new CircularBuffer<double>(5);
private void Update(object sender, EventArgs eventArgs)
{
var value = r.Next(-10, 10);
window.Add(value);
cartesianChart1.Series[0].Values.Add(new ObservableValue(value));
cartesianChart1.Series[1].Values.Add(new ObservableValue(window.Average()));
cartesianChart1.Series[2].Values.Add(new ObservableValue(window.StandardDeviation()));
if (cartesianChart1.Series[0].Values.Count > 12)
{
cartesianChart1.Series[0].Values.RemoveAt(0);
cartesianChart1.Series[1].Values.RemoveAt(0);
cartesianChart1.Series[2].Values.RemoveAt(0);
}
RefreshBindings();
}
}
public static class LiveChartsExtensions
{
public static IEnumerable<T> ToList<T>(this IChartValues values)
{
foreach (var item in values)
yield return (T)Convert.ChangeType(((ObservableValue)item).Value, typeof(T));
}
public static void Bind<T>(this ListBox lb, BindingSource source, IChartValues values)
{
source.DataSource = values.ToList<T>();
lb.DataSource = source;
}
}
}
Imports System.Runtime.CompilerServices
Imports System.Windows.Media
Imports LiveCharts
Imports LiveCharts.Defaults
Imports LiveCharts.Wpf
Public Class Form1
Public Sub New()
InitializeComponent()
InitChart()
SetBindings()
Timer = New Timer() With {
.Interval = 500
}
AddHandler Timer.Tick, AddressOf Update
Timer.Start()
End Sub
Private Sub InitChart()
cartesianChart1.AxisY.Add(New Axis() With {
.Foreground = Brushes.Red,
.Title = "Value"
})
cartesianChart1.Series.Add(New LineSeries() With {
.Stroke = Brushes.Red,
.Fill = Brushes.Transparent,
.StrokeDashArray = New DoubleCollection() From {
2
},
.PointGeometrySize = 6,
.PointGeometry = DefaultGeometries.Diamond,
.ScalesYAt = 0,
.Values = New ChartValues(Of ObservableValue)()
})
cartesianChart1.AxisY.Add(New Axis() With {
.Foreground = New SolidColorBrush(Color.FromArgb(&HFF, &H40, &H95, &HFC)),
.Title = "Average",
.Position = AxisPosition.RightTop,
.Separator = New Separator() With {
.IsEnabled = False
}
})
cartesianChart1.Series.Add(New LineSeries() With {
.Stroke = New SolidColorBrush(Color.FromArgb(&HFF, &H40, &H95, &HFC)),
.Fill = Brushes.Transparent,
.PointGeometrySize = 0,
.LineSmoothness = 0,
.ScalesYAt = 1,
.Values = New ChartValues(Of ObservableValue)()
})
cartesianChart1.AxisY.Add(New Axis() With {
.Foreground = New SolidColorBrush(Color.FromArgb(&HFF, &H0, &H4A, &H26)),
.Title = "Standard Deviation",
.Position = AxisPosition.RightTop,
.Separator = New Separator() With {
.IsEnabled = False
}
})
cartesianChart1.Series.Add(New LineSeries() With {
.Stroke = New SolidColorBrush(Color.FromArgb(&HFF, &H0, &H4A, &H26)),
.Fill = Brushes.Transparent,
.PointGeometrySize = 0,
.LineSmoothness = 0,
.ScalesYAt = 2,
.Values = New ChartValues(Of ObservableValue)()
})
cartesianChart1.AxisX.Add(New Axis() With {
.ShowLabels = False,
.IsEnabled = False
})
End Sub
Private Sub RefreshBindings()
If lbValues.InvokeRequired Then
lbValues.Invoke(DirectCast(Sub() RefreshBindings(), Action))
Return
End If
SetBindings()
RefreshUI()
End Sub
Private valueBinding As New BindingSource()
Private avgBinding As New BindingSource()
Private stdDevBinding As New BindingSource()
Private Sub SetBindings()
lbValues.Bind(Of Integer)(valueBinding, cartesianChart1.Series(0).Values)
lbAverages.Bind(Of Double)(avgBinding, cartesianChart1.Series(1).Values)
lbStdDevs.Bind(Of Double)(stdDevBinding, cartesianChart1.Series(2).Values)
End Sub
Private Sub RefreshUI()
lbValues.Refresh()
lbAverages.Refresh()
lbStdDevs.Refresh()
End Sub
Private Timer As Timer
Private r As New Random()
Private window As New CircularBuffer(Of Double)(5)
Private Sub Update(sender As Object, eventArgs As EventArgs)
Dim value = r.[Next](-10, 10)
window.Add(value)
cartesianChart1.Series(0).Values.Add(New ObservableValue(value))
cartesianChart1.Series(1).Values.Add(New ObservableValue(window.Average()))
cartesianChart1.Series(2).Values.Add(New ObservableValue(window.StandardDeviation()))
If cartesianChart1.Series(0).Values.Count > 12 Then
cartesianChart1.Series(0).Values.RemoveAt(0)
cartesianChart1.Series(1).Values.RemoveAt(0)
cartesianChart1.Series(2).Values.RemoveAt(0)
End If
RefreshBindings()
End Sub
End Class
Module LiveChartsExtensions
<Extension>
Public Iterator Function ToList(Of T)(values As IChartValues) As IEnumerable(Of T)
For Each item In values
Yield Convert.ChangeType(DirectCast(item, ObservableValue).Value, GetType(T))
Next
End Function
<Extension>
Public Sub Bind(Of T)(lb As ListBox, source As BindingSource, values As IChartValues)
source.DataSource = values.ToList(Of T)()
lb.DataSource = source
End Sub
End Module
You can download and try out the app:
*
WinForm C# Version[
^]
*
WinForm VB Version[
^]