3

In my application, I have four series that I want to plot to a Line Graph. Each series is of the same size, three of which are double and the last one is a DateTime list. The three double series come in a list of class objects of type GraphData which look like this:

public class GraphData
{
    public string Name { get; set; }
    public List<double> Data { get; set; }
}

As an additional requirement, I want to have a Y-Axis of its own for each of these.

Here's my entire program so far, and it plots the three graphs on its own axes with no problem.

public partial class MainWindow : Window
{
    public SeriesCollection SeriesCollection { get; set; }
    public AxesCollection YAxesCollection { get; set; }
    public List<GraphData> GraphDatas { get; set; }
    public List<DateTime> TimeStamps { get; set; }

    public MainWindow()
    {
        InitializeComponent();
        GraphDatas = GetGraphData();
        TimeStamps = GetTimeStamps(GraphDatas[0].Data.Count);
        Plot();
    }

    private void Plot()
    {
        SeriesCollection = new SeriesCollection();
        YAxesCollection = new AxesCollection();

        var count = 0;
        foreach (var data in GraphDatas)
        {
            var gLineSeries = new GLineSeries
            {
                Title = data.Name,
                Values = data.Data.AsGearedValues().WithQuality(Quality.Low),
                PointGeometry = null,
                Fill = Brushes.Transparent,
                ScalesYAt = count
            };

            SeriesCollection.Add(gLineSeries);
            YAxesCollection.Add(new Axis() { Title = data.Name });
            count++;
        }

        DataContext = this;
    }

    private List<GraphData> GetGraphData()
    {
        var dataList = new List<GraphData>
        {
            new GraphData() { Name = "DataA", Data = new List<double>() { 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 11.0, 11.0, 9.9, 8.8, 7.7, 6.6, 5.5, 4.4, 3.3, 2.2, 1.1, } },
            new GraphData() { Name = "DataB", Data = new List<double>() { 26, 33, 65, 28, 34, 55, 25, 44, 50, 36, 26, 37, 43, 62, 35, 38, 45, 32, 28, 34 } },
            new GraphData() { Name = "DataC", Data = new List<double>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 } }
        };
        return dataList;
    }

    private List<DateTime> GetTimeStamps(int limit)
    {
        var timeStamps = new List<DateTime>();
        var now = DateTime.Now;
        for (int i = 0; i < limit; i++)
        {
            if (i == 0)
                timeStamps.Add(now);
            else
            {
                now = now.AddDays(1);
                timeStamps.Add(now);
            }
        }
        return timeStamps;
    }
}

My XAML looks simple:

<Grid>
    <lvc:CartesianChart Series="{Binding SeriesCollection}" 
                        AxisY="{Binding YAxesCollection}"
                        DisableAnimations="True"
                        LegendLocation="Right">
    </lvc:CartesianChart>
</Grid>

GetGraphData() and GetTimeStamps() are dummy functions here that simulates my original functions.

Now this works fine, except that the X-axis is not DateTime since obviously I haven't plotted it so. But how would I go about doing this?

The official documentation as well as this SO Post only shows how to do this with only one Y-Axis.

Sach
  • 10,091
  • 8
  • 47
  • 84
  • Are the series timed against the same values, i.e. [circles lined up vertically](https://lvcharts.net/App/examples/v1/wpf/Multiple%20Axes)? – Funk Apr 22 '19 at 16:53
  • Yes they are actually timed against the same values. My actual application deals with log files from another application, which is in the form of a CSV file. The CSV file is then read and each column is converted into a `List`, which is the list in the `GraphData` class. The time stamps are read and put in a list of its own. – Sach Apr 22 '19 at 16:57

1 Answers1

2

I'd start with some changes to the model in order for it to show the full picture. The timestamp is part of the data point and you'll need to wrap them together to allow the Live Charts mapper to plot the data.

public class DataPoint
{
    public DataPoint(DateTime timeStamp, double value)
    {
        TimeStamp = timeStamp;
        Value = value;
    }

    public double Value { get; }
    public DateTime TimeStamp { get; }
}

public class GraphData
{
    public string Name { get; set; }
    public List<DataPoint> Data { get; set; }
}

If you want to keep the current flow of extraction (CSV), you can simply LINQ Zip the data in to its plottable form.

public partial class MainWindow : Window
{
    public SeriesCollection SeriesCollection { get; set; }
    public Func<double, string> Formatter { get; set; }
    public AxesCollection YAxesCollection { get; set; }
    public List<GraphData> GraphDatas { get; set; }

    public MainWindow()
    {
        InitializeComponent();
        var timeStamps = GetTimeStamps(20);
        GraphDatas = GetGraphData(timeStamps);
        Plot();
    }

    private List<GraphData> GetGraphData(List<DateTime> timeStamps)
    {
        var valuesA = new List<double>() { 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 11.0, 11.0, 9.9, 8.8, 7.7, 6.6, 5.5, 4.4, 3.3, 2.2, 1.1, };
        var valuesB = new List<double>() { 26, 33, 65, 28, 34, 55, 25, 44, 50, 36, 26, 37, 43, 62, 35, 38, 45, 32, 28, 34 };
        var valuesC = new List<double>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };

        List<DataPoint> MergeData(List<double> values) => timeStamps.Zip(values, (x, y) => new DataPoint(x, y)).ToList();

        var dataList = new List<GraphData>
        {
            new GraphData() { Name = "DataA", Data = MergeData(valuesA) },
            new GraphData() { Name = "DataB", Data = MergeData(valuesB) },
            new GraphData() { Name = "DataC", Data = MergeData(valuesC) },
        };
        return dataList;
    }

    private void Plot()
    {
        var mapper = Mappers.Xy<DataPoint>()
           .X(dp => (double)dp.TimeStamp.Ticks)
           .Y(dp => dp.Value);

        SeriesCollection = new SeriesCollection(mapper);
        YAxesCollection = new AxesCollection();

        var count = 0;
        foreach (var data in GraphDatas)
        {
            var gLineSeries = new GLineSeries
            {
                Title = data.Name,
                Values = data.Data.AsGearedValues().WithQuality(Quality.Low),
                PointGeometry = null,
                Fill = Brushes.Transparent,
                ScalesYAt = count
            };

            SeriesCollection.Add(gLineSeries);
            YAxesCollection.Add(new Axis() { Title = data.Name });
            count++;
        }

        Formatter = value => new DateTime((long)value).ToString("yyyy-MM:dd HH:mm:ss");

        DataContext = this;
    }

    private List<DateTime> GetTimeStamps(int limit)
    {
        var timeStamps = new List<DateTime>();
        var now = DateTime.Now;
        for (int i = 0; i < limit; i++)
        {
            if (i == 0)
                timeStamps.Add(now);
            else
            {
                now = now.AddDays(1);
                timeStamps.Add(now);
            }
        }
        return timeStamps;
    }
}

XAML

<lvc:CartesianChart Series="{Binding SeriesCollection}" 
                    AxisY="{Binding YAxesCollection}"
                    DisableAnimations="True"
                    LegendLocation="Right">
    <lvc:CartesianChart.AxisX>
        <lvc:Axis LabelFormatter="{Binding Formatter}" />
    </lvc:CartesianChart.AxisX>
</lvc:CartesianChart>
Funk
  • 10,976
  • 1
  • 17
  • 33
  • That did the trick - perfect! Please help me understand here, I have a couple of questions; 1) It seems that since the list in `GraphData` is now a list of `DataPoint` object, this means our time stamp is repeated as many times as there are columns in my CSV, right? 2) So does this mean that LiveCharts actually plot (in this case) 3 different graphs with its own X-Axis for each of them, but only kind of 'shows' just one X-Axis? – Sach Apr 22 '19 at 20:45
  • 1
    1) Correct 2) That's not really [how it works](https://lvcharts.net/App/examples/v1/wpf/Types%20and%20Configuration). For simple value types you get indexed based treatment for free. Given we combine `DateTime` with a `Formatter`, we lose that flexibility. Note there's only one X-Axis though, we just got to be explicit about which timestamp maps to which value. – Funk Apr 23 '19 at 10:41
  • Thanks. I can probably do a test and figure this one out, but I'm guessing then that it plots the X-Axis with the 'first' timestamp list? – Sach Apr 23 '19 at 18:11