Стиль линейного графика LiveCharts на основе значения точки - PullRequest
0 голосов
/ 05 октября 2019

Я рисую несколько линейных графиков, используя LiveCharts и WPF, где содержимое и количество линейных графиков определяются во время выполнения. Поэтому я не знаю заранее, сколько будет LineSeries и каковы будут их значения. Тем не менее, я знаю хороший диапазон для каждого LineSeries. Например, одна серия, назовем это S1, имеет хороший диапазон 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;
    }
}

Я нашел здесь пример , что PointState имеет цветную основуна значениях, но это не работает для меня, потому что я рисую несколько серий в одном. Кроме того, на моем графике тысячи точек, поэтому я отключил PointGeometry, так как, если я включу их, их будет очень трудно увидеть.

Возможно ли вообще то, что я хочу?

1 Ответ

1 голос
/ 11 октября 2019

Я нашел решение, на самом деле 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];
            }
        }
    }
}

в результате вы видите все плохие детали с цветом RED

enter image description here

...