I am using LiveCharts
to plot several line charts on the same graph. Some of the charts have missing data points.
Current graph with gaps:
I would like to connect across these gaps:
The goal if possible:
MainWindow.xaml
<Window x:Class="LiveChartsTest.MainWindow"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<lvc:CartesianChart Series="{Binding Series}">
<lvc:CartesianChart.AxisX>
<lvc:Axis Title="Date" Labels="{Binding Labels}"/>
</lvc:CartesianChart.AxisX>
</lvc:CartesianChart>
</Grid>
</Window>
MainWindow.xaml.cs
using LiveCharts;
using LiveCharts.Wpf;
using System;
using System.Windows;
namespace LiveChartsTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// Create date labels
Labels = new string[10];
for (int i = 0; i < Labels.Length; i++)
{
Labels[i] = DateTime.Now.Add(TimeSpan.FromDays(i)).ToString("dd MMM yyyy");
}
Series = new SeriesCollection
{
new LineSeries
{
Title = "Dataset 1",
Values = new ChartValues<double>
{
4,
5,
7,
double.NaN,
double.NaN,
5,
2,
8,
double.NaN,
6
}
},
new LineSeries
{
Title = "Dataset 2",
Values = new ChartValues<double>
{
2,
3,
4,
5,
6,
3,
1,
4,
5,
3
}
}
};
DataContext = this;
}
public SeriesCollection Series { get; set; }
public string[] Labels { get; set; }
}
}
Is there any way to do this with LiveCharts?
That looks more like a math trigonometry problem, where you have to figure out the coordinates of the missing points and add them to SeriesCollection
so you can end up with that flat looing joints between the fragments.
Consider the following explanatory picture based on your graph:
Between the X and Y we have to deduce two points, A and B (we know that we need two in between because we can deduce that from the interval between X and Y or we can simply count the NaN
s in the initial collection).
A & B Y coordinates could be easily deduced using what we already know and the angle α.
We are looking to calculate the |BB'| and |AA'| sizes (added to the distance between y and the index should represent the final A and B)
We know basically that: tan(α)= |BB'|/|B'Y| = |AA'|/|A'Y| = |XZ|/|ZY|
For simplicity now let's assume that all intervals in the X-axis and Y-axis are equal 1, I will come back to this later.
Now we do know |XZ|/|ZY|, (xz is the difference between x and y, and zy is basically how many NaN
s there is in between), so we can easily calculate |BB'| and |AA'|:
Here how a basic implementation to what was explained above looks like (the code should be self-explanatory):
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// Create date labels
Labels = new string[10];
for (int i = 0; i < Labels.Length; i++)
{
Labels[i] = DateTime.Now.Add(TimeSpan.FromDays(i)).ToString("dd MMM yyyy");
}
var chartValues = new ChartValues<double>
{
4,
5,
7,
double.NaN,
double.NaN,
5,
2,
8,
double.NaN,
6
};
Series = new SeriesCollection
{
new LineSeries
{
Title = "Dataset 1",
Values = ProcessChartValues(chartValues)
},
new LineSeries
{
Title = "Dataset 2",
Values = new ChartValues<double>
{
2,
3,
4,
5,
6,
3,
1,
4,
5,
3
}
}
};
DataContext = this;
}
private ChartValues<double> ProcessChartValues(ChartValues<double> chartValues)
{
var tmpChartValues = new ChartValues<double>();
double bornLeft =0, bornRight=0;
double xz = 0, zy = 0, xy = 0;
bool gapFound = false;
foreach (var point in chartValues)
{
if (!double.IsNaN(point))
{
if (gapFound)
{
// a gap was found and it needs filling
bornRight = point;
xz = Math.Abs(bornLeft - bornRight);
for (double i = zy; i >0; i--)
{
tmpChartValues.Add((xz / zy) * i + Math.Min(bornLeft, bornRight));
}
tmpChartValues.Add(point);
gapFound = false;
zy = 0;
}
else
{
tmpChartValues.Add(point);
bornLeft = point;
}
}
else if(gapFound)
{
zy += 1;
}
else
{
zy += 1;
gapFound = true;
}
}
return tmpChartValues;
}
public SeriesCollection Series { get; set; }
public string[] Labels { get; set; }
}
And here the output:
Now coming back to our interval size, notice how the fragments aren't sharp because of our interval=1 assumption, but on the other hand, this assumption gave the graph some smoothness which is most likely what anyone would be after. If you still need to have sharp fragments, you could explore the LiveChart
API to get that interval in pixels which I am not sure they offer (then simply multiply with xz and zy sizes) otherwise you could deduce it from the ActualWidth
and ActualHight
of the chart drawn area.
As a final note, the code should be extended to handle the NaN
points on the edges (you have to either neglect them or define a direction to which the graph should go).