【问题标题】:LiveCharts Line Graph style based on point valueLiveCharts 基于点值的折线图样式
【发布时间】:2019-10-04 22:14:59
【问题描述】:

我使用LiveChartsWPF绘制了一堆折线图,其中的内容和折线图的数量是在运行时确定的。所以我事先不知道会有多少LineSeries,它们的值是多少。但是,我知道每个 LineSeriesgood 范围。例如,一个系列,我们称之为S1,其good 范围为 2+/-1。所以1到3之间的任何东西都被认为是好的。类似地,可能还有另一个,比如S2,范围是 30+/-2,所以 28 到 32 之间的任何值都是好的。

我想绘制折线图,​​以便将范围内的部分绘制为实线,但如果部分超出范围,它将是虚线/虚线。由于我有多个LineSeries,因此我将每个都绘制在自己的 Y 轴上。我的XAML 和代码如下所示:

<Grid>
    <lvc:CartesianChart Name="MyChart" Margin="4"
                        Series="{Binding SeriesCollection}"/>
</Grid>

后面的代码:

public partial class MainWindow : Window, INotifyPropertyChanged
{
    public SeriesCollection SeriesCollection { get; set; }

    public MainWindow()
    {
        InitializeComponent();
        PlotGraph();
    }

    private void PlotGraph()
    {
        SeriesCollection = new SeriesCollection();
        var lineSeries1 = new LineSeries
        {
            Title = "S1",
            Values = new ChartValues<double>() { 2.3, 2.0, 3.1, 1.3, 0.5, 3.8, 7.3, 2.4, 1.2, 0.1 },
            DataLabels = true,
            Stroke = Brushes.Green,
            Fill = Brushes.Transparent,
            ScalesYAt = 0
        };

        var lineSeries2 = new LineSeries
        {
            Title = "S2",
            Values = new ChartValues<double>() { 32.5, 34.5, 29.5, 26.0, 25.8, 30.5, 32.1, 36.5, 32.4, 24.5 },
            DataLabels = true,
            Stroke = Brushes.HotPink,
            Fill = Brushes.Transparent,
            ScalesYAt = 1
        };

        SeriesCollection.Add(lineSeries1);
        SeriesCollection.Add(lineSeries2);

        MyChart.AxisY.Add(new Axis());
        MyChart.AxisY.Add(new Axis());

        DataContext = this;
    }
}

I found an example here PointState 是根据值着色的,但它对我不起作用,因为我在一个中绘制了多个系列。另外,我的图表有数千个点,所以我禁用了PointGeometry,因为如果我启用它们,它们无论如何都很难看到。

我想要的有可能吗?

【问题讨论】:

    标签: c# wpf linegraph livecharts


    【解决方案1】:

    我找到了一个解决方案,实际上是 2:您可以针对您的问题自定义实时图表,或者您像我在下面那样重新计算不同点:

    它只是一种方式,一种回答你问题的想法,一切皆有可能,但需要一些代码....

    绘图方法

        private void PlotGraph()
        {
            var points = new List<Point>() { new Point(0, 2.3), new Point(1, 2.0),
                                             new Point(2, 3.1), new Point(3, 1.3),
                                             new Point(4, 0.5), new Point(5, 3.8),
                                             new Point(6, 7.3), new Point(7, 2.4),
                                             new Point(8, 1.2), new Point(9, 0.1)};
    
            var range1 = new double[] { 1d, 3d };
    
            var otherpoints = CurvesMath.GetInterpolatedCubicSplinedCurve(points);
            var pointscurve = otherpoints.Select(p => p.Y).ToArray();
    
            SeriesCollection = new SeriesCollection();
            var lineSeries1 = new LineSeries
            {
                Title = "S1",
                Values = new ChartValues<double>(pointscurve),
                DataLabels = false,
                Stroke = Brushes.Transparent,
                Fill = Brushes.Transparent,
                ScalesYAt = 0,
                PointGeometrySize = 2,
                Configuration = Mappers.Xy<double>()
                                       .X((value, index) => index)
                                       .Y((value, index) => value)
                                       .Stroke((value, index) => value <= range1[0] || value >= range1[1] ? Brushes.Red : Brushes.Blue)
                                       .Fill((value, index) => value <= range1[0] || value >= range1[1] ? Brushes.Red : Brushes.Blue)
        };
    
            points = new List<Point>() { new Point(0, 32.5), new Point(1, 34.5),
                                         new Point(2, 29.5), new Point(3, 26.0),
                                         new Point(4, 25.8), new Point(5, 30.5),
                                         new Point(6, 32.1), new Point(7, 36.5),
                                         new Point(8, 32.4), new Point(9, 24.5)};
            var range2 = new double[] { 28d, 32d };
            otherpoints = CurvesMath.GetInterpolatedCubicSplinedCurve(points);
            pointscurve = otherpoints.Select(p => p.Y).ToArray();
            var lineSeries2 = new LineSeries
            {
                Title = "S2",
                Values = new ChartValues<double>(pointscurve),
                DataLabels = false,
                Stroke = Brushes.Transparent,
                Fill = Brushes.Transparent,
                ScalesYAt = 1,
                PointGeometrySize = 2,
                Configuration = Mappers.Xy<double>()
                                       .X((value, index) => index)
                                       .Y((value, index) => value)
                                       .Stroke((value, index) => value <= range2[0] || value >= range2[1] ? Brushes.Red : Brushes.Green)
                                       .Fill((value, index) => value <= range2[0] || value >= range2[1] ? Brushes.Red : Brushes.Green)
            };
    
            SeriesCollection.Add(lineSeries1);
            SeriesCollection.Add(lineSeries2);
    
            MyChart.AxisY.Add(new Axis());
            MyChart.AxisY.Add(new Axis());
            DataContext = this;
        }
    

    xaml 文件:

    <Grid>
        <lvc:CartesianChart Name="MyChart" Margin="4"
                        Series="{Binding SeriesCollection}"  >
        </lvc:CartesianChart>
    </Grid>
    

    插值三次样条(或贝塞尔曲线)

    using System.Collections.Generic;
    using System.Linq;
    using System.Windows;
    
    namespace WpfApp2
    {
        public static class CurvesMath
        {
            private const int precision = 80;
    
            public static List<Point> GetInterpolatedCubicSplinedCurve(IList<Point> points)
            {
                var output = new List<Point>();
                int np = points.Count; // number of points
                double[] yCoords = new double[np]; // Newton form coefficients
                double[] xCoords = new double[np]; // x-coordinates of nodes
                double y;
                double x;
    
                if (np > 0)
                {
                    for (int i = 0; i < np; i++)
                    {
                        var p = points[i];
                        xCoords[i] = p.X;
                        yCoords[i] = p.Y;
                    }
                    if (np > 1)
                    {
                        double[] a = new double[np];
                        double x1;
                        double x2;
                        double[] h = new double[np];
                        for (int i = 1; i <= np - 1; i++)
                        {
                            h[i] = xCoords[i] - xCoords[i - 1];
                        }
                        if (np > 2)
                        {
                            double[] sub = new double[np - 1];
                            double[] diag = new double[np - 1];
                            double[] sup = new double[np - 1];
    
                            for (int i = 1; i <= np - 2; i++)
                            {
                                diag[i] = (h[i] + h[i + 1]) / 3;
                                sup[i] = h[i + 1] / 6;
                                sub[i] = h[i] / 6;
                                a[i] = (yCoords[i + 1] - yCoords[i]) / h[i + 1] - (yCoords[i] - yCoords[i - 1]) / h[i];
                            }
                            SolveTridiag(sub, diag, sup, ref a, np - 2);
                        }
    
                        output.Add(points.First());
    
                        for (int i = 1; i <= np - 1; i++)
                        {
                            // loop over intervals between nodes
                            for (int j = 1; j <= precision; j++)
                            {
                                x1 = (h[i] * j) / precision;
                                x2 = h[i] - x1;
                                y = ((-a[i - 1] / 6 * (x2 + h[i]) * x1 + yCoords[i - 1]) * x2 +
                                     (-a[i] / 6 * (x1 + h[i]) * x2 + yCoords[i]) * x1) / h[i];
                                x = xCoords[i - 1] + x1;
    
                                output.Add(new Point(x, y));
                            }
                        }
                    }
                }
                return output;
            }
    
            public static double SolveCubicSpline(IList<Point> knownSamples, double z)
            {
                int np = knownSamples.Count;
    
                if (np > 1)
                {
                    if (knownSamples[0].X == z) return knownSamples[0].Y;
    
                    double[] a = new double[np];
                    double x1;
                    double x2;
                    double y;
                    double[] h = new double[np];
    
                    for (int i = 1; i <= np - 1; i++)
                    {
                        h[i] = knownSamples[i].X - knownSamples[i - 1].X;
                    }
    
                    if (np > 2)
                    {
                        double[] sub = new double[np - 1];
                        double[] diag = new double[np - 1];
                        double[] sup = new double[np - 1];
    
                        for (int i = 1; i <= np - 2; i++)
                        {
                            diag[i] = (h[i] + h[i + 1]) / 3;
                            sup[i] = h[i + 1] / 6;
                            sub[i] = h[i] / 6;
    
                            a[i] = (knownSamples[i + 1].Y - knownSamples[i].Y) / h[i + 1] -
                                   (knownSamples[i].Y - knownSamples[i - 1].Y) / h[i];
                        }
    
                        // SolveTridiag is a support function, see Marco Roello's original code
    
                        // for more information at
    
                        // http://www.codeproject.com/useritems/SplineInterpolation.asp
    
                        SolveTridiag(sub, diag, sup, ref a, np - 2);
                    }
    
    
    
                    int gap = 0;
    
                    double previous = double.MinValue;
    
                    // At the end of this iteration, "gap" will contain the index of the interval
    
                    // between two known values, which contains the unknown z, and "previous" will
    
                    // contain the biggest z value among the known samples, left of the unknown z
    
                    for (int i = 0; i < knownSamples.Count; i++)
                    {
                        if (knownSamples[i].X < z && knownSamples[i].X > previous)
                        {
                            previous = knownSamples[i].X;
                            gap = i + 1;
                        }
                    }
    
                    x1 = z - previous;
                    if (gap > h.Length - 1)
                        return z;
    
                    x2 = h[gap] - x1;
    
                    if (gap == 0)
                        return 0.0;
    
                    y = ((-a[gap - 1] / 6 * (x2 + h[gap]) * x1 + knownSamples[gap - 1].Y) * x2 +
                         (-a[gap] / 6 * (x1 + h[gap]) * x2 + knownSamples[gap].Y) * x1) / h[gap];
    
                    return y;
                }
                return 0;
            }
    
            private static void SolveTridiag(double[] sub, double[] diag, double[] sup, ref double[] b, int n)
            {
                /*                  solve linear system with tridiagonal n by n matrix a
                                    using Gaussian elimination *without* pivoting
                                    where   a(i,i-1) = sub[i]  for 2<=i<=n
                                            a(i,i)   = diag[i] for 1<=i<=n
                                            a(i,i+1) = sup[i]  for 1<=i<=n-1
                                    (the values sub[1], sup[n] are ignored)
                                    right hand side vector b[1:n] is overwritten with solution 
                                    NOTE: 1...n is used in all arrays, 0 is unused */
                int i;
                /*                  factorization and forward substitution */
                for (i = 2; i <= n; i++)
                {
                    sub[i] = sub[i] / diag[i - 1];
                    diag[i] = diag[i] - sub[i] * sup[i - 1];
                    b[i] = b[i] - sub[i] * b[i - 1];
                }
                b[n] = b[n] / diag[n];
                for (i = n - 1; i >= 1; i--)
                {
                    b[i] = (b[i] - sup[i] * b[i + 1]) / diag[i];
                }
            }
        }
    }
    

    在结果中,您会看到所有坏的部分都是红色的

    【讨论】:

    • 如果这就是你所说的,我不想有这样的片段。 lvcharts.net/App/examples/v1/wpf/Sections 我的图表一次甚至可以有 7-8 个折线图,如果我做了太多这样的阴影线段,就很难理解任何事情。我希望根据它是否超过最大/最小值来绘制实线或虚线/虚线。
    • @Sach 我已经编辑了我的答案,我认为这是我能解决你的问题的最好方法
    • 我建议你使用散点而不是线系列,首先可以提高性能,但你也可以显示一些点
    • 这确实解决了问题,但不幸的是真的太慢了​​,无法实际使用;我的实际图表有数千个点。我仍然会接受答案,因为它非常聪明并且解决了问题!
    • 最好的办法是直接修改livecharts程序(见github),但需要一些技巧。但我认为如果你使用 Scattered points 而不是lineseries,速度会大大提高
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-24
    • 2023-04-02
    • 2015-07-26
    • 2012-08-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多