LiveCharts 中运行时的多轴

Multiple Axis at Runtime in LiveCharts

我正在尝试编写一个 WPF 应用程序来为我拥有的一系列数字绘制折线图。这些数字列在 .CSV 文件中,我将在 运行 时阅读该文件。因此,我不知道我将拥有的系列数,也不知道每个系列的 max/min 值。

为了演示和简洁起见,请看下面的示例。将这些系列值视为我将从我的实际应用程序中的 .CSV 文件中读取的值。

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        SeriesCollection = new SeriesCollection
        {
            new LineSeries
            {
                Title = "Series 1",
                Values = new ChartValues<double> { 4, 66, 5, 2, 4 },
            },
            new LineSeries
            {
                Title = "Series 2",
                Values = new ChartValues<double> { 6, 7, 3, 4, 6 },
            },
            new LineSeries
            {
                Title = "Series 3",
                Values = new ChartValues<double> { 4, 2, 7, 2, 7 },
            }
        };

        DataContext = this;
    }

    public SeriesCollection SeriesCollection { get; set; }
}

我的XAML看起来很简单,像这样:

<Window x:Class="WPFCharts.MainWindow"
        ...
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <lvc:CartesianChart Series="{Binding SeriesCollection}"/>
    </Grid>
</Window>

如您所见,如果我使用默认 LiveCharts 设置将其绘制在折线图中,则一个系列中的一个值与其他值相比超出了图表:

所以我想给用户机会把这样的折线图放在他们自己的坐标轴上。通过阅读 LiveCharts 文档,我发现 as shown here,您可以使用 ScaleXAtScaleYAt 属性将不同的线系列放在不同的轴上。

然而,该示例在 XAML 中设置了轴,而我想动态地执行此操作。所以我尝试在后面的代码中设置上述 属性 ,如下所示:

SeriesCollection = new SeriesCollection
{
    new LineSeries
    {
        Title = "Series 1",
        Values = new ChartValues<double> { 4, 66, 5, 2, 4 },
        ScalesYAt = 0
    },
    new LineSeries
    {
        Title = "Series 2",
        Values = new ChartValues<double> { 6, 7, 3, 4, 6 },
        ScalesYAt = 1
    },
    new LineSeries
    {
        Title = "Series 3",
        Values = new ChartValues<double> { 4, 2, 7, 2, 7 },
        ScalesYAt = 2
    }
};

但是当我这样做时,运行 应用程序出现异常:

System.ArgumentOutOfRangeException: 'Index was out of range. Must be non-negative and less than the size of the collection.'

我在这里做错了什么?我如何使用代码设置它,而不是 XAML?

如果你想使用不同的Y轴,那么你需要声明它们,也许你错过了。所以你的模型会变成这样:

public class ViewModel
{
    public ViewModel()
    {
        SeriesCollection = new SeriesCollection
        {
            new LineSeries
            {
                Title = "Series 1",
                Values = new ChartValues<double> { 4, 66, 5, 2, 4 },
                ScalesYAt = 0
            },
            new LineSeries
            {
                Title = "Series 2",
                Values = new ChartValues<double> { 6, 7, 3, 4, 6 },
                ScalesYAt = 1
            },
            new LineSeries
            {
                Title = "Series 3",
                Values = new ChartValues<double> { 4, 2, 7, 2, 7 },
                ScalesYAt = 2
            }
        };

        AxisYCollection = new AxesCollection
        {
            new Axis { Title = "Y Axis 1", Foreground = Brushes.Gray },
            new Axis { Title = "Y Axis 2", Foreground = Brushes.Red },
            new Axis { Title = "Y Axis 3", Foreground = Brushes.Brown }
        };
    }

    public AxesCollection AxisYCollection { get; set; }

    public SeriesCollection SeriesCollection { get; set; }

}

而 XAML 将是:

<Grid>
    <lvc:CartesianChart Series="{Binding SeriesCollection}" AxisY="{Binding AxisYCollection}" />
</Grid>

当然你需要设置一个ViewModel class的实例作为你的Window的DataContext:

public MainWindow()
{
    vm = new ViewModel();

    InitializeComponent();
    DataContext = vm;
}

如果您不在绑定 AxesCollection 中声明 "enough" 轴,将找不到它在 n 索引处的元素,您将陷入 ArgumentOutOfRangeException.希望对你有帮助。

我正在做类似的事情;现在一团糟,我把它作为一种学习经验。

using CsvHelper;
using LiveCharts.Geared;
using LiveCharts.Wpf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Windows.Forms;
using System.Windows.Media;
using System.ComponentModel;
using System.Data;

namespace CSVtoCHART
{
    public partial class FrmCSVtoCHART : Form
    {
        public FrmCSVtoCHART()
        {
            InitializeComponent();
            LoadSettings();
            InitializeDataGridSettings();
            cartesianChart1.AxisX.Add(new Axis
            {
                Title = "Time",
                Labels = new string[] { "0", "1", "2", "3", "4" },
                MinValue = 0,
                MaxValue = 4,
                LabelsRotation = -45,
                Foreground = Brushes.Black
            });
            cartesianChart1.AxisY.Add(new Axis
            {
                Foreground = Brushes.Black,
                Title = "Chart",
                MinValue = 0,
                MaxValue = 100
            });
            cartesianChart1.Zoom = LiveCharts.ZoomingOptions.X;
        }

        public DataTable dt = new DataTable();

        private void BtnOpenCSV_Click(object sender, EventArgs e)
        {
            string FilePath;
            if (openFileDialog1.ShowDialog() == DialogResult.OK)
            {
                cartesianChart1.AxisX.Clear();
                cartesianChart1.AxisY.Clear();
                cartesianChart1.Series.Clear();

                FilePath = openFileDialog1.FileName;
                using (var streamReader = new StreamReader(@FilePath))
                using (var csv = new CsvReader(streamReader, CultureInfo.InvariantCulture))
                {
                    csv.Read();
                    csv.ReadHeader();
                    csv.Read();

                    int num = 0;
                    foreach (var header in csv.HeaderRecord)
                    {
                        if (double.TryParse(csv.GetField(num), out double value))
                        {
                            dt.Columns.Add(header, typeof(double));
                        }
                        else if (TryConvertStringToDateTime(csv.GetField(num), out DateTime ts))
                        {
                            dt.Columns.Add(header, typeof(string));
                        }

                        num++;
                    }

                    while (csv.Read())
                    {
                        num = 0;
                        var row = dt.NewRow();
                        foreach (DataColumn column in dt.Columns)
                        {
                            if (double.TryParse(csv.GetField(num), out double value))
                            {
                                row[column.ColumnName] = value;
                            }
                            else if (TryConvertStringToDateTime(csv.GetField(num), out DateTime ts))
                            {
                                row[column.ColumnName] = csv.GetField(num);
                            }
                            else if (column.DataType == typeof(double))
                            {
                                row[column.ColumnName] = 0; //double.NaN;
                            }

                            num++;
                        }
                        dt.Rows.Add(row);
                    }

                    var dgRow = new List<RowSettings>();
                    num = 0;
                    foreach (DataColumn column in dt.Columns)
                    {
                        if (column.DataType == typeof(double))
                        {
                            var brushes = typeof(Brushes).GetProperties()
                                .Where(pi => pi.PropertyType == typeof(SolidColorBrush))
                                .Select(pi => pi.Name)
                                .ToList();
                            var random = new Random();
                            int index = random.Next(brushes.Count);
                            var brush = typeof(Brushes).GetProperty(brushes[index])
                                .GetValue(null)
                                as SolidColorBrush;

                            List<double> vals = dt.AsEnumerable().Select(v => v.Field<double>(column.ColumnName)).Distinct().ToList();
                            double minVal = vals.Min();
                            double maxVal = vals.Max();
                            dgRow.Add(new RowSettings
                            (
                                column.ColumnName,
                                brushes[index],
                                true,
                                minVal,
                                maxVal
                            ));

                            AddYSeries(num, brushes[index], column, minVal, maxVal);

                            num++;
                        }
                        if (column.DataType == typeof(string))
                        {
                            var col = dt.AsEnumerable().Select(c => c.Field<string>(column.ColumnName)).ToList();
                            cartesianChart1.AxisX.Add(new Axis
                            {
                                Title = "Time",
                                Labels = col,
                                LabelsRotation = -45,
                                Foreground = Brushes.Black,
                                MinValue = 0,
                                MaxValue = 10000
                            });
                        }
                    }

                    dataGridView1.DataSource = dgRow;
                }
            }
        }

        private void AddYSeries(int num, string lineColor, DataColumn column, double minVal, double maxVal)
        {
            var brush = typeof(Brushes).GetProperty(lineColor)
                            .GetValue(null)
                            as SolidColorBrush;

            var col = dt.AsEnumerable().Select(c => c.Field<double>(column.ColumnName)).ToList();
            cartesianChart1.Series.Add(new LineSeries
            {
                Title = column.ColumnName,
                Values = col.AsGearedValues().WithQuality(Quality.Low),
                ScalesYAt = num,
                LineSmoothness = 0,
                StrokeThickness = 1,
                Fill = Brushes.Transparent,
                PointGeometry = null,
                Stroke = brush
                
            });

            cartesianChart1.AxisY.Add(new Axis
            {
                Foreground = brush,
                Title = column.ColumnName,
                MinValue = minVal,
                MaxValue = maxVal,
                DisableAnimations = true
            });
        }

        private void InitializeDataGridSettings()
        {
            var brushes = typeof(Brushes).GetProperties()
                             .Where(pi => pi.PropertyType == typeof(SolidColorBrush))
                             .Select(pi => pi.Name)
                             .ToList();
            DataGridViewColumn[] dgCol =
            {
                new DataGridViewTextBoxColumn()
                {
                    HeaderText = "Plot Name",
                    SortMode = DataGridViewColumnSortMode.NotSortable,
                    DataPropertyName = "PlotName",
                    ValueType = typeof(string),
                    ReadOnly = true
                },
                new DataGridViewCheckBoxColumn()
                {
                    HeaderText = "Display on chart?",
                    SortMode = DataGridViewColumnSortMode.NotSortable,
                    DataPropertyName = "Display",
                    ValueType = typeof(bool),
                    ReadOnly = false
                },
                new DataGridViewComboBoxColumn()
                {
                    HeaderText = "Line Color",
                    DataSource = brushes,
                    SortMode = DataGridViewColumnSortMode.NotSortable,
                    DataPropertyName = "LineColor",
                    ValueType = typeof(string),
                    ReadOnly = false
                },
                new DataGridViewComboBoxColumn()
                {
                    HeaderText = "Line Style",
                    DataSource = new List<string>
                    {
                        "Solid",
                        "Dash Dash",
                        "Dash Dot",
                        "Dot Dot",
                    },
                    SortMode = DataGridViewColumnSortMode.NotSortable,
                    DataPropertyName = "LineStyle",
                    MaxDropDownItems = 4,
                    ValueType = typeof(List<string>),
                    ReadOnly = false
                },
                new DataGridViewTextBoxColumn()
                {
                    HeaderText = "Chart Min",
                    SortMode = DataGridViewColumnSortMode.NotSortable,
                    DataPropertyName = "ChartMin",
                    ValueType = typeof(double),
                    ReadOnly = false
                },
                new DataGridViewTextBoxColumn()
                {
                    HeaderText = "Chart Max",
                    SortMode = DataGridViewColumnSortMode.NotSortable,
                    DataPropertyName = "ChartMax",
                    ValueType = typeof(double),
                    ReadOnly = false
                }
            };
            dataGridView1.Columns.AddRange(dgCol);
            dataGridView1.RowHeadersVisible = false;
            dataGridView1.RowHeadersWidthSizeMode = DataGridViewRowHeadersWidthSizeMode.DisableResizing;
        }

        public bool TryConvertStringToDateTime(string s, out DateTime result)  // function for converting string to datetime
        {
            return ((DateTime.TryParse(s, out result)) ||
                    (DateTime.TryParseExact(s, "yyyy-MM-dd H:mm", CultureInfo.InvariantCulture, DateTimeStyles.None, out result)) ||
                    (DateTime.TryParseExact(s, "yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture, DateTimeStyles.None, out result)) ||
                    (DateTime.TryParseExact(s, "yyyy-MM-dd h:mm:ss tt", CultureInfo.InvariantCulture, DateTimeStyles.None, out result)) ||
                    (DateTime.TryParseExact(s, "yyyy-MM-dd hh:mm:ss tt", CultureInfo.InvariantCulture, DateTimeStyles.None, out result)) ||
                    (DateTime.TryParseExact(s, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out result)) ||
                    (DateTime.TryParseExact(s, "yyyy/MM/dd H:mm", CultureInfo.InvariantCulture, DateTimeStyles.None, out result)) ||
                    (DateTime.TryParseExact(s, "yyyy/MM/dd HH:mm", CultureInfo.InvariantCulture, DateTimeStyles.None, out result)) ||
                    (DateTime.TryParseExact(s, "yyyy/MM/dd h:mm:ss tt", CultureInfo.InvariantCulture, DateTimeStyles.None, out result)) ||
                    (DateTime.TryParseExact(s, "yyyy/MM/dd hh:mm:ss tt", CultureInfo.InvariantCulture, DateTimeStyles.None, out result)) ||
                    (DateTime.TryParseExact(s, "yyyy/MM/dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out result)) ||
                    (DateTime.TryParseExact(s, "MM.dd.yy HH:mm", CultureInfo.InvariantCulture, DateTimeStyles.None, out result))
                   );
        }

        private void LoadSettings()
        {
            this.Height = Properties.Settings.Default.FormHeight;
            this.Width = Properties.Settings.Default.FormWidth;
            this.Location = Properties.Settings.Default.FormLocation;
        }

        private void FrmCSVtoCHART_FormClosing(object sender, FormClosingEventArgs e)
        {
            Properties.Settings.Default.FormHeight = this.Height;
            Properties.Settings.Default.FormWidth = this.Width;
            Properties.Settings.Default.FormLocation = this.Location;
            Properties.Settings.Default.Save();
        }

        private void btnApply_Click(object sender, EventArgs e)
        {
            cartesianChart1.AxisY.Clear();
            cartesianChart1.Series.Clear();
            int num = 0;
            List<string> showSeries = new List<string>();
            List<string> seriesColor = new List<string>();
            List<double> seriesMin = new List<double>();
            List<double> seriesMax = new List<double>();
            foreach (DataGridViewRow row in dataGridView1.Rows)
            {
                if (Convert.ToBoolean(row.Cells[1].Value))
                {
                    showSeries.Add(row.Cells[0].Value.ToString());
                    seriesColor.Add(row.Cells[2].Value.ToString());
                    seriesMin.Add(Convert.ToDouble(row.Cells[4].Value));
                    seriesMax.Add(Convert.ToDouble(row.Cells[5].Value));
                }
            }
            foreach (DataColumn column in dt.Columns)
            {
                if (showSeries.Contains(column.ColumnName))
                {
                    AddYSeries(num, seriesColor[num], column, seriesMin[num], seriesMax[num]);
                    num++;
                }
            }
        }
    }
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CSVtoCHART
{
    class RowSettings
    {
        private string _PlotName, _LineColor, _LineStyle;
        private bool _Display;
        private double _ChartMin, _ChartMax;
        public RowSettings(string _PlotName, string _LineColor, bool _Display, double _ChartMin, double _ChartMax)
        {
            this._PlotName = _PlotName;
            this._LineColor = _LineColor;
            this._Display = _Display;
            this._ChartMin = _ChartMin;
            this._ChartMax = _ChartMax;
        }
        public string PlotName 
        { 
            get 
            {
                return _PlotName; 
            } 
            set 
            { 
                _PlotName = value; 
            }
        }
        public bool Display
        {
            get
            {
                return _Display;
            }
            set
            {
                _Display = value;
            }
        }
        public string LineColor
        {
            get
            {
                return _LineColor;
            }
            set
            {
                _LineColor = value;
            }
        }
        public string LineStyle
        {
            get
            {
                return _LineStyle;
            }
            set
            {
                _LineStyle = value;
            }
        }
        public double ChartMin
        {
            get
            {
                return _ChartMin;
            }
            set
            {
                _ChartMin = value;
            }
        }
        public double ChartMax
        {
            get
            {
                return _ChartMax;
            }
            set
            {
                _ChartMax = value;
            }
        }
    }
}