LiveCharts WPF 实时数据缓慢。提高 LiveCharts 实时绘图性能

2024-02-20

我正在研究在 WPF 应用程序中使用 LiveChart 来实时绘制温度测量结果。 我整理了一个简单的折线图示例,以 10Hz 读取数据,并为每个样本重新绘制。然而,我发现重绘率约为 1Hz。对于 WPF 实时图表工具来说,这似乎非常慢。 我的xaml如下:

<lvc:CartesianChart x:Name="TemperatureChart" Grid.Row="1" LegendLocation="Right" Hoverable="False" DataTooltip="{x:Null}">
    <lvc:CartesianChart.Series>
        <lvc:LineSeries x:Name="TempDataSeries" Values="{Binding TemperatureData}"></lvc:LineSeries>
    </lvc:CartesianChart.Series>
</lvc:CartesianChart>

我的视图模型片段如下:

ChartValues<ObservableValue> _temperatureData = new ChartValues<ObservableValue>();

public ChartValues<ObservableValue> TemperatureData
{
    get => this._temperatureData;
    set => this._temperatureData = value;
}

void Initialise()
{
    _temperatureMonitor.Subscribe(ProcessTemperatures);
}

void TestStart()
{
    _temperatureMonitor.Start();
}
void TestStop()
{
    _temperatureMonitor.Stop();
}
void ProcessTemperatures(TemperatureData data)
{
    TemperatureData.Add(data.Temperature);
}

我没有处理大量数据,并且已使用 100 个值的限制进行了测试。我确信我的读取数据的线程几乎没有开销,但是重绘一次会绘制大约 10 个点。

我是否正确实现了绑定? 我是否需要添加属性通知来强制更新?我的理解是这是由 ChartValues 处理的。

Thanks.

更新。 Oxyplot 通过绑定到数据点的 ObservableCollection 产生了如下所示的所需结果。使用 LiveCharts 获得相同的性能会很不错,因为它具有非常好的美观性。


该库的实施相当糟糕。有一个付费版本,其宣传自己比免费版本性能更高。我没有测试过付费版本。免费版本的图表控件非常慢,尤其是在处理巨大的数据集时。

显然,默认CartesianChart.AnimationSpeed默认设置为 500ms。在实时场景中将绘图速率提高到 1/450 毫秒以上将导致“丢失”帧。 “丢失”意味着数据最终可见,但不是实时绘制的。每个布局失效的渲染过程花费的时间太长。
超过 450 毫秒将使绘图显得滞后(由于跳帧)。这是执行不力的结果。当超过 500 毫秒的默认动画速度时,应禁用动画。

无论如何,您可以采取一些措施来提高整体性能,以便显着超过 450 毫秒:

  • Use ObservablePoint or ObservableValue或者通常让你的数据类型实现INotifyPropertyChanged。修改固定/不可变的数据项集而不是修改源集合(例如通过添加/删除项)时,您可能会获得更好的结果。
  • 通过设置删除图形的实际视觉点元素LineSeries.PointGeometry to null。这将删除额外的渲染元素。线条本身将保持可见。这会显著地提高性能。
  • Set Chart.Hoverable to false禁用鼠标悬停效果。
  • Set Chart.DataTooltip to {x:Null}禁用工具提示对象的创建。
  • Set Chart.DisableAnimations to true。禁用动画将显著地提高渲染性能。或者通过设置禁用每个轴的选择性动画Axis.DisableAnimations.
  • Set Axis.MinValue and Axis.MaxValue禁用每次值更改时的自动缩放。在大多数 X 轴值发生变化的情况下,您也必须实时调整这两个属性。
  • Set Axis.Unit还显着改善了重新渲染的外观。
  • Set UIElement.CacheMode在图表对象上。用一个BitmapCache允许禁用像素捕捉并修改渲染缩放。 ABitmapCache.RenderAtScale值低于1增加了模糊度,但也提高了渲染性能UIElement.

以下示例通过移动每个点来实时绘制正弦图ObservablePoint左边一组固定的 360 个值的值。应用所有建议的性能调整,从而在 1/10ms (100Hz) 的绘图速率下获得可接受的平滑度。您可以使用 1/50ms 到 1/200ms 之间的值,甚至可以低于 1/10ms(如果这仍然可以接受)。
请注意,默认的 Windows 计时器以 15.6 毫秒的分辨率运行。这意味着

强烈建议调整采样率以匹配绘图速率,以避免出现滞后感。或者实现生产者-消费者模式以避免丢失/跳过数据读取。

数据模型.cs

public class DataModel : INotifyPropertyChanged
{
  public DataModel()
  {
    this.ChartValues = new ChartValues<ObservablePoint>();
    this.XMax = 360;
    this.XMin = 0;

    // Initialize the sine graph
    for (double x = this.XMin; x <= this.XMax; x++)
    {
      var point = new ObservablePoint() 
      { 
        X = x, 
        Y = Math.Sin(x * Math.PI / 180) 
      };
      this.ChartValues.Add(point);
    }

    // Setup the data mapper
    this.DataMapper = new CartesianMapper<ObservablePoint>()
      .X(point => point.X)
      .Y(point => point.Y)
      .Stroke(point => point.Y > 0.3 ? Brushes.Red : Brushes.LightGreen)
      .Fill(point => point.Y > 0.3 ? Brushes.Red : Brushes.LightGreen);

    // Setup the IProgress<T> instance in order to update the chart (UI thread)
    // from the background thread 
    var progressReporter = new Progress<double>(newValue => ShiftValuesToTheLeft(newValue, CancellationToken.None));

    // Generate the new data points on a background thread 
    // and use the IProgress<T> instance to update the chart on the UI thread
    Task.Run(async () => await StartSineGenerator(progressReporter, CancellationToken.None));
  }

  // Dynamically add new data
  private void ShiftValuesToTheLeft(double newValue, CancellationToken cancellationToken)
  {
    // Shift item data (and not the items) to the left
    for (var index = 0; index < this.ChartValues.Count - 1; index++)
    {
      cancellationToken.ThrowIfCancellationRequested();

      ObservablePoint currentPoint = this.ChartValues[index];
      ObservablePoint nextPoint = this.ChartValues[index + 1];
      currentPoint.X = nextPoint.X;
      currentPoint.Y = nextPoint.Y;
    }

    // Add the new reading
    ObservablePoint newPoint = this.ChartValues[this.ChartValues.Count - 1];
    newPoint.X = newValue;
    newPoint.Y = Math.Sin(newValue * Math.PI / 180);

    // Update axis min/max
    this.XMax = newValue;
    this.XMin = this.ChartValues[0].X;
  }

  private async Task StartSineGenerator(IProgress<double> progressReporter, CancellationToken cancellationToken)
  {
    while (true)
    {
      // Add the new reading by posting the callback to the UI thread
      ObservablePoint newPoint = this.ChartValues[this.ChartValues.Count - 1];
      double newXValue = newPoint.X + 1;
      progressReporter.Report(newXValue);

      // Check if CancellationToken.Cancel() was called 
      cancellationToken.ThrowIfCancellationRequested();

      // Plot at 1/10ms
      await Task.Delay(TimeSpan.FromMilliseconds(10), cancellationToken);
    }
  }

  private double xMax;
  public double XMax
  {
    get => this.xMax;
    set
    {
      this.xMax = value;
      OnPropertyChanged();
    }
  }

  private double xMin;
  public double XMin
  {
    get => this.xMin;
    set
    {
      this.xMin = value;
      OnPropertyChanged();
    }
  }

  private object dataMapper;   
  public object DataMapper
  {
    get => this.dataMapper;
    set 
    { 
      this.dataMapper = value; 
      OnPropertyChanged();
    }
  }

  public ChartValues<ObservablePoint> ChartValues { get; set; }
  public Func<double, string> LabelFormatter => value => value.ToString("F");

  public event PropertyChangedEventHandler PropertyChanged;
  protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) => this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

主窗口.xaml

<Window>
  <Window.DataContext>
    <DataModel />
  </Window.DataContext>

  <CartesianChart Height="500" 
                  Zoom="None"  
                  Hoverable="False" 
                  DataTooltip="{x:Null}" 
                  DisableAnimations="True">
    <wpf:CartesianChart.Series>
      <wpf:LineSeries PointGeometry="{x:Null}"
                      Title="Sine Graph"
                      Values="{Binding ChartValues}"
                      Configuration="{Binding DataMapper}"/>
    </wpf:CartesianChart.Series>

    <CartesianChart.CacheMode>
      <BitmapCache EnableClearType="False" 
                   RenderAtScale="1"
                   SnapsToDevicePixels="False" />
    </CartesianChart.CacheMode>

    <CartesianChart.AxisY>
      <Axis Title="Sin(X)"
            FontSize="14" 
            Unit="1"
            MaxValue="1.1"
            MinValue="-1.1" 
            DisableAnimations="True"
            LabelFormatter="{Binding LabelFormatter}"
            Foreground="PaleVioletRed" />
    </CartesianChart.AxisY>

    <CartesianChart.AxisX>
      <Axis Title="X" 
            DisableAnimations="True" 
            FontSize="14" 
            Unit="1"
            MaxValue="{Binding XMax}"
            MinValue="{Binding XMin}"
            Foreground="PaleVioletRed" />
    </CartesianChart.AxisX>
  </CartesianChart>
</Window>
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

LiveCharts WPF 实时数据缓慢。提高 LiveCharts 实时绘图性能 的相关文章

  • 在 VS2017 下使用 Conan 和 CMake 项目进行依赖管理

    我正在尝试使用 CMake 与 VS2017 集成为 C 设置一个开发环境 以便在 Linux x64 下进行编译 为了更好地管理依赖关系 我选择使用 Conan 但我对这个软件还很陌生 我想知道让 VS2017 识别项目依赖关系的最佳方法
  • 如何使用 zlib 制作 .zip 文件

    我正在阅读zlib的文档 它相当详细 但我读到了这一行 输出数据将位于zlib格式 与 gzip 或zip formats http www zlib net zlib how html http www zlib net zlib how
  • Subversion 和 Visual Studio 项目的最佳实践

    我最近开始在 Visual Studio 中处理各种 C 项目 作为大型系统计划的一部分 该系统将用于替换我们当前的系统 该系统是由用 C 和 Perl 编写的各种程序和脚本拼凑而成的 我现在正在进行的项目已经达到了颠覆的临界点 我想知道什
  • 将字节数组转换为托管结构

    更新 这个问题的答案帮助我编写了开源项目GitHub 上的 AlicanC 现代战争 2 工具 https github com AlicanC AlicanC s Modern Warfare 2 Tool 你可以看到我是如何阅读这些数据
  • (const T v) 在 C 中从来都不是必需的,对吗?

    例如 void func const int i 在这里 const是不必要的 因为所有参数都是按值传递的 包括指针 真的吗 C 中的所有参数确实都是按值传递 这意味着无论您是否包含该参数 实际参数都不会改变const or not 然而
  • 从 C 结构生成 C# 结构

    我有几十个 C 结构 我需要在 C 中使用它们 典型的 C 结构如下所示 typedef struct UM EVENT ULONG32 Id ULONG32 Orgin ULONG32 OperationType ULONG32 Size
  • 带 If 的嵌套 For 循环的时间复杂度

    void f int n for int i 1 i lt n i if i int sqrt n 0 for int k 0 k lt pow i 3 k do something 我的思考过程 执行if语句的次数 sum i 1 to
  • 如何生成 appsettings..json 文件?

    我有一个 ASP NET Core 2 WebAPI 它将部署在以下环境中 INT QA STAGE 生产环境 基于上述 我需要有appsettings
  • TcpClient 在异步读取期间断开连接

    我有几个关于完成 tcp 连接的问题 客户端使用 Tcp 连接到我的服务器 在接受客户端后listener BeginAcceptTcpClient ConnectionEstabilishedCallback null 我开始阅读netw
  • 将带有 glut 的点击坐标添加到向量链接列表中

    我想创建一个向量链接列表 并在 GLUT 库的帮助下获取点击的位置并将它们附加到链接列表中 这些是我写的结构 typedef struct vector int x int y Vector typedef struct VectorLis
  • 2D morton 码编码/解码 64 位

    如何将给定 x y 的莫顿代码 z 顺序 编码 解码为 32 位无符号整数 生成 64 位莫顿代码 反之亦然 我确实有 xy2d 和 d2xy 但仅适用于 16 位宽的坐标 产生 32 位莫顿数 在网上查了很多 但没有找到 请帮忙 如果您可
  • 为什么具有相同名称但不同签名的多个继承函数不会被视为重载函数?

    以下代码片段在编译期间产生 对 foo 的调用不明确 错误 我想知道是否有任何方法可以解决此问题而不完全限定对 foo 的调用 include
  • 默认析构函数做了多少事情

    C 类中的默认析构函数是否会自动删除代码中未显式分配的成员 例如 class C public C int arr 100 int main void C myC new C delete myC return 0 删除 myC 会自动释放
  • tabcontrol selectedindex 更改事件未被触发 C#

    嘿伙计们 我有一个很小的问题 请参阅下面的代码 this is main load private void Form1 Load object sender EventArgs e tabAddRemoveOperator Selecte
  • .NET 客户端中 Google 表格中的条件格式请求

    我知道如何在 Google Sheets API 中对值和其他格式进行批量电子表格更新请求 但条件格式似乎有所不同 我已正确设置请求 AddConditionalFormatRuleRequest formatRequest new Add
  • WPF。如何从另一个窗口隐藏/显示主窗口

    我有两个窗口 MainWindow 和 Login 显示登录的按钮位于主窗口 this Hide Login li new Login li Show 登录窗口上有一个检查密码的按钮 如果密码正确 我如何显示主窗口 将参数传递给 MainW
  • Visual Studio 2017 完全支持 C99 吗?

    Visual Studio 的最新版本改进了对 C99 的支持 最新版本VS2017现在支持所有C99吗 如果没有 C99 还缺少哪些功能 No https learn microsoft com en us cpp visual cpp
  • 受限 AppDomain 中的代码访问安全异常

    Goal 我需要在权限非常有限的 AppDomain 中运行一些代码 它不应该访问任何花哨或不安全的内容 except对于我在其他地方定义的一些辅助方法 我做了什么 我正在创建一个具有所需基本权限的沙箱 AppDomain 并创建一个运行代
  • 以 UTF8 而不是 UTF16 输出 DataTable XML

    我有一个 DataTable 我正在使用 WriteXML 创建一个 XML 文件 尽管我在以 UTF 16 编码导出它时遇到问题 并且似乎没有明显的方法来更改它 我了解 NET 在字符串内部使用 UTF 16 这是正确的吗 然后 我通过
  • 服务器响应 PASV 命令返回的地址与建立 FTP 连接的地址不同

    System Net WebException 服务器响应 PASV 命令返回的地址与建立 FTP 连接的地址不同 在 System Net FtpWebRequest CheckError 在 System Net FtpWebReque

随机推荐