Как лучше всего нарисовать диаграмму с дискретными значениями на холсте? - PullRequest
0 голосов
/ 27 декабря 2018

У меня есть холст с динамической шириной.Мне нужно нарисовать график на этом холсте.Из-за количества измеренных значений и специфики бизнес-сферы невозможно использовать сторонние элементы управления и компоненты.

Диаграмма показывает уровень некоторых измерений, которые являются дискретными.Ось X указывает время измерения.В худшем случае каждый пиксель графика может иметь разный уровень.Вот так это выглядит так: Chart

Мой первый подход, чтобы заставить его работать, состоял в том, чтобы нарисовать линию для каждого пикселя.Мой код выглядит так:

        MyCanvas.Children.Clear();
        var random = new Random();
        for (var i = 0; i < MyCanvas.Width; i++)
        {
            var line = new Line()
            {
                X1 = i,
                X2 = i,
                Y1 = MyCanvas.ActualHeight,
                Y2 = MyCanvas.ActualHeight - random.Next(0, (int)MyCanvas.ActualHeight),
                Stroke = Brushes.Blue
            };

            MyCanvas.Children.Add(line);
        }

Этот код делает то, что я хочу.Он рисует диаграмму, подобную этой: Chart

Но кажется, что это не оптимальный способ сделать такие вещи, как этот.Моя диаграмма должна поддерживать панорамирование и масштабирование, и для перерисовки диаграммы при каждом запросе пользователя требуется около 200-350 мс.Это слишком много (1000/350 = 2,85 кадров в секунду).

У меня нет большого опыта работы с WPF, поэтому мой вопрос - какой самый оптимальный способ нарисовать такой график?Возможно, мне нужно использовать объекты Paths и Geometry, но я не могу с уверенностью сказать, что это даст значительный выигрыш в производительности, пока я не реализую его.Также я не знаю, какую геометрию мне нужно использовать.Кажется, что LineGeometry соответствует моим ожиданиям.

Заранее спасибо.

Ответы [ 2 ]

0 голосов
/ 02 января 2019

У меня будет еще один шанс ответить на этот вопрос, без привязки данных на этот раз.Учитывая потребность в высокой производительности, наилучшим подходом, вероятно, будет использование пользовательского FrameworkElement.Вот пример, который рисует 10000 сэмплов и способен поддерживать 60 кадров в секунду на моем ноутбуке, причем каждый сэмпл обновляется один раз за кадр:

public class FastChart : FrameworkElement
{
    private Pen ChartPen;
    private const int MaxSampleVal = 1000;
    private const int NumSamples = 10000;
    private int[] Samples = new int[NumSamples];

    public FastChart()
    {
        this.ChartPen = new Pen(Brushes.Blue, 1);
        if (this.ChartPen.CanFreeze)
            this.ChartPen.Freeze();
        ClipToBounds = true;
        this.SnapsToDevicePixels = true;
        CompositionTarget.Rendering += CompositionTarget_Rendering;

        var rng = new Random();
        for (int i = 0; i < NumSamples; i++)
            this.Samples[i] = MaxSampleVal / 2;
    }

    private void CompositionTarget_Rendering(object sender, EventArgs e)
    {
        // update all samples
        var rng = new Random();
        for (int i = 0; i < NumSamples; i++)
            this.Samples[i] = Math.Max(0, Math.Min(MaxSampleVal, this.Samples[i] + rng.Next(11) - 5));

        // force an update
        InvalidateVisual();
    }

    protected override void OnRender(DrawingContext drawingContext)
    {
        // hacky clip to parent scroller
        var minx = 0;
        var maxx = NumSamples;
        var scroller = this.Parent as ScrollViewer;
        if (scroller != null)
        {
            minx = Math.Min((int)scroller.HorizontalOffset, NumSamples - 1);
            maxx = Math.Min(NumSamples, minx + (int)scroller.ViewportWidth);
        }

        for (int x = minx; x < maxx; x++)
            drawingContext.DrawLine(this.ChartPen, new Point(0.5 + x, 1000), new Point(0.5 + x, 1000 - this.Samples[x]));
    }

}

Несколько вещей, на которые следует обратить внимание:

  1. ручка, которую вы используете для визуализации линий, должна быть заморожена, в противном случае это приведет к значительному снижению производительности.
  2. если ваша диаграмма находится внутри ScrollViewer (скажем), вы захотите закрепить ее навидимая часть экрана.В моем примере выше я только что взломал его, проверив, является ли родитель ScrollViewer, вам может потребоваться сделать это по-разному в зависимости от ваших обстоятельств.
  3. , как вы можете видеть, я подписываюсь на CompositionTarget.Rendering обновить значения графика и форсировать рендер.известно, что иногда он вызывается более одного раза за кадр, но для этого есть обходные пути, поэтому я предоставлю вам возможность его найти.

В любом случае, вот пример того, как выбудет использовать этот элемент управления (здесь я жестко запрограммировал ширину, очевидно, вы хотите связать его со свойством в вашей модели представления или чем-то подобным):

<ScrollViewer x:Name="theScrollViewer" HorizontalScrollBarVisibility="Auto">
    <controls:FastChart Width="10000" />
</ScrollViewer>

А вот еще кадр из (анимация) результат:

enter image description here

Единственная проблема с этим состоит в том, что он не использует Canvas, но я думаю, чтобы получить желаемую производительностьвам придется отказаться от этого требования.

0 голосов
/ 27 декабря 2018

Очень легко сделать, но я настоятельно рекомендую использовать привязку данных.

  1. Создайте ItemsControl.
  2. Дайте ItemsControl ItemsPanel типа Canvas.
  3. Свяжите ширину и высоту холста с элементами в вашей модели представления, указав экстенты каждой оси.
  4. Оберните все это в окне просмотра, чтобы оно автоматически масштабировалось, независимо от ширины и высоты.
  5. Для каждого элемента в вашем списке ItemsControl создайте модель представления, содержащую X1 / Y1 / X2 / Y2, затем используйте DataTemplate для визуализации этих элементов, используя эти значения для рисования вертикальной линии.

Итакв основном ваша модель основного вида должна выглядеть примерно так (которая в порядке демонстрации генерирует случайные точки данных):

public class MainViewModel : ObservableObject
{
    private IEnumerable<DataPointViewModel> _DataPoints;
    public IEnumerable<DataPointViewModel> DataPoints
    {
        get { return this._DataPoints; }
        set { Set(() => this.DataPoints, ref this._DataPoints, value); }
    }

    public int ChartWidth { get; } = 1000;
    public int ChartHeight { get; } = 1000;

    public MainViewModel()
    {
        var rng = new Random();
        this.DataPoints = Enumerable.Range(0, this.ChartWidth)
            .Select(x => new DataPointViewModel {X1 = x, Y1 = this.ChartHeight-1, X2 = x, Y2 = this.ChartHeight - 1 - rng.Next(this.ChartHeight) });
    }
}

Модель представления для элементов в вашей коллекции должна обеспечивать точки для линии, котораяиспользуется для их визуализации:

public class DataPointViewModel : ObservableObject
{
    private double _X1;
    public double X1
    {
        get { return this._X1; }
        set { Set(() => this.X1, ref this._X1, value); }
    }

    private double _Y1;
    public double Y1
    {
        get { return this._Y1; }
        set { Set(() => this.Y1, ref this._Y1, value); }
    }

    private double _X2;
    public double X2
    {
        get { return this._X2; }
        set { Set(() => this.X2, ref this._X2, value); }
    }

    private double _Y2;
    public double Y2
    {
        get { return this._Y2; }
        set { Set(() => this.Y2, ref this._Y2, value); }
    }
}

И затем ваш XAML просто использует их для визуализации строки для каждой точки данных в элементе управления Canvas:

<Viewbox>
    <ItemsControl ItemsSource="{Binding DataPoints}" Width="{Binding ChartWidth}" Height="{Binding ChartHeight}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Canvas />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Line Stroke="Blue" StrokeThickness="1" X1="{Binding X1}" Y1="{Binding Y1}" X2="{Binding X2}" Y2="{Binding Y2}" />
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Viewbox>

Все это, в свою очередь, генерирует следующий вывод:

enter image description here

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...