实时数据的WPF图表/可视化

11

我一直在尝试找到在WPF中以折线图的形式呈现实时数据的合适方法。实时数据指的是从一个USB设备收集的数据,该设备以大约40Hz的速率生成数据。我正在异步读取多达7个流的40Hz数据。

我尝试使用了两个现成的解决方案(WPF Toolkit图表和Swordfish图表),并几乎查看了Dynamic Data Visualization组件,但在阅读其论坛上的一些评论后放弃了。似乎现成的图表解决方案都针对静态图表,而我实际上需要类似于Windows任务管理器的东西——只是速度更快,数据点更多。

目前,我已经开发了自己的解决方案,迄今为止似乎效果最好,但我觉得我可能错过了什么,因为似乎我应该能够获得更好的性能。

要求是它应该能够处理一个滑动窗口内约10000个点的恒定范围-随着新数据的进入(以40Hz),旧数据被推到可见范围之外向左移动。它需要至少持续20-30分钟(每个数据流约75-100万个点)。

我的当前自定义实现基于继承自Shape的组件,并使用StreamingGeometry作为DefinigGeometry。从设备传入的数据通过队列传递给该组件,以提高性能,由于固有的“突发效应”,在出队操作之后,该组件将无效。

所以,我的问题是,我走在正确的道路上还是完全错误?在WPF中实现这样的数据可视化最有效的方法是什么?任何帮助或提示都将不胜感激。


1
DirectX互操作是方向。有些人会使用SlimDX,但它是一个巨大的依赖和抽象的学习曲线。我建议直接使用DX9或(通过DXGI的DX11),因为它与WPF D3DImage接口握手。仍然有一个学习曲线,但与slimdx相同,并且更易于维护,以创建DX设备、上下文、缓冲区、着色器并使您的应用程序使用Direct3D。文档很糟糕,但足够好,完成后,您已经完成了一些很酷的东西,而不依赖于其他人。请查看我们的现实世界18M图表演示,其中包括WPF WinForm MFC EXE,主要是音频WAV和GIS数据。 - Robert
@Robert,感谢您的建议。理想情况下,我想坚持使用DX9以与XP兼容,但到目前为止,我无法找到一种方法来绘制任意粗的线条,并以与WPF实现的锯齿路径相当的质量进行比较。有什么指针吗?您是否不得不编写自己的镶嵌例程?如果我可以在性能/质量方面得到我想要的东西,我甚至可以接受DX10。 - Mike Dinescu
粗线是D3D的最大缺点,但这并不是什么大问题。如果您可以围绕已知的图表大小进行设计,则顶点缓冲逻辑将知道大小,并且只需将线绘制为四边形(双三角形)即可。如果您不想基于大小事件重建顶点缓冲区,则应该设计着色器以在常量缓冲区中保存纵横比和物理大小设置,然后在顶点着色器中使用此数据来实时调整线条四边形。这适用于dx9/11。或者,您可以使用D2D和数据减少逻辑来绘制大量数据。 - Robert
此外,Win7 Win8 的美妙之处在于合成 D2D 和 D3D。因此,当您需要高级图形,例如粗线条、简化文本等,并且不需要大量使用时,您可以将 D2D 和 D3D 结合在同一渲染中,几乎看不到性能损失。我们的演示使用 12M 数据点音频数据 wav 示例 123 和厚垂直线注释,在主图和缩放窗口中显示播放位置。同样适用于网格中的粗线条等。 - Robert
4个回答

6
如前所述,WPF“标准”方式不能获得所需的性能。尝试了几种不同的免费和商业产品,但都没有得到我需要的结果,因此开始尝试以下方法:
  1. 使用WPF几何图形。
  2. 使用Direct2D。
两者在性能方面都不好。
我知道的一件事是,WPF很擅长渲染图像(BitmapSource),因此我决定沿着这个方向前进,并使用WriteableBitmapEx在WriteableBitmap上绘制图形,然后传递它...
WriteableBitmapEx库的一个问题是其没有像GDI那样多的绘图功能。那么为什么不直接使用GDI呢?只要正确操作,效果并无差别。
示例:
    public void BeginDraw()
    {
        _writeable_bitmap = new WriteableBitmap((int)Math.Max(_size.Width, 1), 
        (int)Math.Max(_size.Height, 1), 96.0, 96.0, PixelFormats.Pbgra32, null);

        _gdi_bitmap = new System.Drawing.Bitmap(_writeable_bitmap.PixelWidth, 
        _writeable_bitmap.PixelHeight,_writeable_bitmap.BackBufferStride,
        System.Drawing.Imaging.PixelFormat.Format32bppPArgb,
        _writeable_bitmap.BackBuffer);

        _writeable_bitmap.Lock();

        _g = System.Drawing.Graphics.FromImage(_gdi_bitmap);
        _g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;

        _g.Clear(System.Drawing.Color.Transparent);
    }

    public void DrawSeries(IEnumerable<System.Drawing.PointF> points)
    {
        _g.DrawCurve(dataSeries.GdiPen, points.ToArray());
    }

    public void EndDraw()
    {
        _writeable_bitmap.AddDirtyRect(new Int32Rect(0, 0, 
        _writeable_bitmap.PixelWidth, _writeable_bitmap.PixelHeight));
        _writeable_bitmap.Unlock();

        var cloned = _writeable_bitmap.Clone();
        cloned.Freeze();

        Dispatcher.BeginInvoke(new Action((() =>
        {
            Image = cloned;
        })));

        _g.Dispose();
    }

如您所见,我正在使用一种“特殊技巧”,通过GDI直接将图形绘制到本地WPF BitmapSource中。

我不确定这是否是正确的测量方法,但使用此方法,我已经成功获得了一些非常不错的结果:

  • 超过10亿个数据点。
  • 5FPS的刷新率。
  • 每秒100次推送1万个新数据点。
  • i7 Core笔记本电脑上10%的CPU。
  • WPF UI和渲染线程完全可以执行其他操作。

我是开源库RealTimeGraphX(适用于WPF和UWP)的作者,它正是这样做的。 https://github.com/royben/RealTimeGraphX

除了实际的线系列之外,其余的图形组件都是使用标准的WPF控件和形状制作的,因此它们可以轻松定制和操作。


你好!我已经成功地实现了你的库,它比LiveCharts强大得多!但是我无法添加缩放和平移功能!请问该怎么做? - Matheus Stumpf
只需按住左 Ctrl 键并拖动鼠标即可。 - Roy Ben Shabat

4

声明:我拥有ABT软件和SciChart,同时也为WriteableBitmapEx开源库做出了贡献

不幸的是,你没有错过任何东西。在WPF / Silverlight中保留模式渲染引擎对于此类工作的性能表现非常差。我曾经参与过许多从Windows Forms升级到WPF的系统项目,在这些项目中客户对这个“GPU加速”框架的渲染性能非常失望!

但是,还有一种方法。使用即时模式渲染。查看WriteableBitmap或InteropBitmap类。有一个名为WriteableBitmapEx的优秀开源库,由Rene Schulte开发,我也为其做出了贡献。WriteableBitmapEx提供了一些低级绘图函数(GDI风格),用于直接绘制位图。这提供了极佳的性能和低内存占用(是的,微软花哨的框架被一些优化良好的for循环和指针字节数组所击败)。

如果您正在寻找特定的第三方图表组件,请尝试使用SciChart。SciChart是我自己开发的一个组件,旨在填补WPF或Silverlight科学/股票图表的超高性能差距。它使用专有的重新采样算法在绘制之前减少数据集,立即模式渲染和一系列其他优化,如对象池和资源重用,从而实现非常大数据集的平滑刷新速率和低内存占用率。
点击上面链接中的性能演示(需要Silverlight 4)。目前,SciChart能够以约5FPS(取决于目标硬件)的速度渲染100万个数据点,相当于每秒500万个数据点。商业许可证将在2012年第一季度推出。

1
@DrABT - 出于好奇,WritableBitmapEx是否支持任意抗锯齿厚度,还是您需要在SciChart组件中单独实现? - Mike Dinescu
1
嗨Miky,就我所知,WBEX不支持这个。我们自己实现了一些不同的算法,以在质量和速度之间取得良好的折衷。 WBEX论坛上讨论过一个简单的算法,其中您可以预缓存大小为N的椭圆并在Bresenham线中的每个点处绘制它。令人惊讶的是,这起作用,但会导致锯齿状边缘,因为WBEX具有整数坐标。要获得真正平滑的宽线,您需要浮点数 - 这在CPU方面也很昂贵! SciChart高质量渲染器和实验性D3D渲染器都可以做到这一点。 - Dr. Andrew Burnett-Thompson
@DrABT 感谢您的快速回复。我目前并没有积极地在这方面工作,但也许我会在某个时候重新审视我们的实现。 - Mike Dinescu

3

通过低级别的DirectX编程和利用HLSL着色器,可以实现最佳效果。当需要最大性能和实时性时,应立即忘记基于System.Windows.Media命名空间的渲染。

我们能够开发出例程,可以绘制超过10亿个数据点,例如8个数据源x125 M数据点,使用宽线,无下采样。这些例程是LightningChart、WPF图表(和WinForms图表)的一部分。我们花了7年时间才达到这个水平……我们做了一个10亿点示例,包括VS项目和Youtube视频。

[我是LightningChart的技术主管]


嗨Pasi - 感谢您的回答; LightningChart是开源的吗?如果不是,您介意分享一些有关您使用的低级DirectX原语和着色器类型的详细信息吗?您是否正在使用Direct2D或Direct3D?您用着色器做什么? - Mike Dinescu
嗨,Mike,LightningChart不是开源的。我们也在使用Direct3D进行2D图形处理。Direct2D有些慢和受限制。很抱歉我不能在公共场合分享所有细节。我们的性能是多年工作、尝试不同方法和客户实际需求的结果。 - Pasi Tuomainen
我在您的网站上找不到任何定价信息,它的价格是多少?看起来相当不错。有一个评论,希望您使用适当的捕获设备而不是拍摄监视器,那个演示看起来很糟糕! - Chris

2
WPF的保留模式渲染使得绘制/重绘自定义图表和图片变得棘手,尤其是当这些图形包含大量对象时,要使其性能良好。使用WritableBitmap并通过WritePixels调用填充是我在WPF中能够实现的最快速的绘制方法,这可能是您的选择。它远远超过了我使用PathGeometries和绘制到Canvas所编写的图表的绘制速度。我很想看看是否有更快的中间方式。

一个可能的“折中方案”是在OnRender重写中将其绘制到RenderContext上。如果没有其他选择,这会给人一种巨大的精神放松感,因为它更像是传统的GDI/WinForms绘画...(更严肃地说,这应该提供了访问良好渲染原语的方式,而无需构建保留模式树,这往往只是自己对象图的冗余重复) - Will Dean
我怕 OnRender 仍在使用保留模式管道。你所做的只是使用它将保留模式对象发布到图形管道中。要测试它,在里面设置一个断点,然后最小化/最大化窗口。如果这是一个立即模式方法(例如 Winforms 中的 OnPaint),则会强制重新绘制。我不100%确定,但我认为调整大小也不会导致 OnRender 启动,除非您明确将其设置为标志。 - Dr. Andrew Burnett-Thompson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接