在Canvas上用离散值绘制图表的最佳方法是什么? [英] What's the best way to draw chart with discrete values on Canvas?

查看:65
本文介绍了在Canvas上用离散值绘制图表的最佳方法是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个具有动态宽度的画布.我需要在此Canvas上绘制图表.由于测量值的数量和业务领域的特殊性,无法使用第三方控件和组件.

I have a Canvas with dynamic width. I need to draw a chart on this Canvas. Due to the amount of the measured values and the business area specificity it's not possible to use third-party controls and components.

该图显示了一些离散测量的级别.X轴表示测量时间.在最坏的情况下,图表中的每个像素都可能具有不同的级别.所以看起来像这样:

The chart shows the level of some measurement that is discrete. The X axis indicates the time of the measurement. In the worst case every pixel of the chart can have different level. So it looks like this:

我要做的第一个方法就是为每个像素画一条线.所以我的代码看起来像这样:

My first approach just to make it working was to draw a line for every pixel. So my code looks like this:

        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);
        }

此代码可以执行我想要的操作.它绘制了这样一个图表:

This code does what I want it to do. It draws a chart like this one:

但是,这似乎并不是执行此操作的最佳方法.我的图表应支持平移和缩放,并且在每个用户请求上重绘图表大约需要200-350ms.太高了(1000/350 = 2.85fps).

But it seems that it is not an optimal way to do things like this one. My chart should support panning and zooming and it takes around 200-350ms to redraw a chart on every user request. That's too much (1000/350 = 2.85fps).

我在WPF中经验不足,所以我的问题是-绘制此类图表的最佳方法是什么?也许我需要使用路径"和几何"对象,但是我不能肯定地说,在实现之前,它会带来很大的性能提升.我也不知道我需要使用哪种几何.看来LineGeometry符合我的期望.

I don't have much experience in WPF so my question is - what is the most optimal way to draw such chart? Maybe I need to use Paths and Geometry objects but I can't say for sure that it will give much perfomance gain until I implement it. Also I don't know what kind of Geometry I need to use. It seems that LineGeometry matches my expectations.

谢谢.

推荐答案

我将回答这个问题,而这次没有数据绑定.鉴于需要高性能,最好的方法可能是使用自定义FrameworkElement.这是一个示例,该示例绘制10,000个样本并能够在我的笔记本电脑上保持60fps的速度,每个样本每帧更新一次:

I'll have another shot at answering this, without data binding this time. Given the need for high performance the best approach would probably be to use a custom FrameworkElement. Here's an example that draws 10,000 samples and is able to maintain 60fps on my laptop, with each sample being updated once per frame:

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]));
    }

}

这里需要注意的几件事:

A few things to note here:

  1. 用于渲染线条的笔需要冻结,否则将导致性能大幅下降.
  2. 如果您的图表位于ScrollViewer中(例如),则需要剪切到屏幕的可见部分.在上面的示例中,我只是通过检查父级是否为ScrollViewer来破解它,您可能需要根据具体情况采取不同的操作.
  3. 如您所见,
  4. 我正在订阅 CompositionTarget.Rendering 来更新图表值并强制渲染.有时每帧它被称为多次,但是有一些解决方法,因此我将留给您查找.
  1. the pen you use to render the lines needs to be frozen, failing to do this will result in a huge performance decrease.
  2. if your chart is inside a ScrollViewer (say) you'll want to clip to the visible portion of the screen. In my example above I've just hacked it in by checking if the parent is a ScrollViewer, you may need to do this differently depending on your circumstances.
  3. as you can see, I'm subscribing to CompositionTarget.Rendering to update the chart values and force a render. this is known to sometimes be called more than once per frame, but there are workarounds for this so I'll leave it to you to find one.

无论如何,这是您如何使用此控件的示例(我在此处对宽度进行了硬编码,显然您希望将其绑定到视图模型中的属性或其他内容):

In any case, here's an example of how you would use this control (I've hard-coded the width here, obviously you'd want to binding it to a property in your view model or something):

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

这是(动画)结果的静止图像:

And here's a still from the (animating) result:

唯一的问题是它不使用Canvas,但我认为为了获得所需的性能,您必须放弃该要求.

The only problem with this is that it doesn't use Canvas, but I think in order to get the performance you want you're going to have to ditch that requirement.

这篇关于在Canvas上用离散值绘制图表的最佳方法是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆