首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >LiveCharts WPF对实时数据的处理速度较慢。提高LiveCharts实时绘图性能

LiveCharts WPF对实时数据的处理速度较慢。提高LiveCharts实时绘图性能
EN

Stack Overflow用户
提问于 2020-07-28 16:09:43
回答 2查看 7.6K关注 0票数 10

我正在研究LiveChart在WPF应用程序中的使用,以便在实时、温度测量中进行绘图。我已经建立了一个简单的线图例子,以读取数据在10赫兹,并重新绘制每一个样本。然而,我发现重绘率在1Hz左右。对于WPF动态图表工具来说,这似乎非常慢。我的xaml如下:

代码语言:javascript
运行
复制
<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>

我的视图模型中的片段如下所示:

代码语言:javascript
运行
复制
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处理的。

谢谢。

更新。Oxyplot通过绑定到ObservableColllection of DataPoints,产生了以下所需的结果。使用LiveCharts获得相同的性能会很好,因为它具有非常好的美观。

EN

Stack Overflow用户

回答已采纳

发布于 2020-08-01 16:14:31

这个图书馆实现得相当差。有一个付费版本,它宣传自己比免费版本更有表现力。我还没有测试付费版本。免费版本的图表控件非常慢,特别是在处理庞大的数据集时。

显然,默认的CartesianChart.AnimationSpeed默认设置为500 is。在实时场景中增加1/450 in以上的绘图速率将导致“丢失”帧。“丢失”意味着数据最终是可见的,但不是实时绘制的。每个布局无效的呈现传递只需太长时间。

超过450 to将使情节显得滞后(由于跳过的帧)。这是执行不力的结果。当超出默认的动画速度500 of时,应该禁用动画。

无论如何,为了大大超越450 go,您可以做一些事情来提高整体性能:

  • 使用ObservablePointObservableValue,或者通常让数据类型实现INotifyPropertyChanged。当修改一个固定/不变的数据项集合而不是修改源集合时,您可能会获得更好的结果,例如,通过将items.
  • Remove LineSeries.PointGeometry 设置为 null**.来添加/删除图形的实际可视点元素。这将删除其他呈现元素。直线笔划本身将保持可见。这将使_significantly_ performance.**
  • Set 改进到false以禁用鼠标效果。
  • Chart.DataTooltip设置为{x:Null}以禁用工具提示objects.
  • Set Chart.DisableAnimations E 238 true**.的创建。禁用动画将使_significantly_ 提高呈现性能。或者,通过设置** Axis.DisableAnimations**.**
  • Set、Axis.MinValueAxis.MaxValue来禁用对每个值更改的自动缩放,从而禁用对每个轴选择的动画。在x轴值变化的大多数情况下,您也必须实时调整这两个属性。
  • Set Axis.Unit也显着地改善了图表对象在UIElement.CacheMode上的外观。使用BitmapCache可以禁用像素快照和修改呈现缩放。低于UIElement.

BitmapCache.RenderAtScale值会增加模糊度,但也会呈现1的性能。

下面的示例通过将360个固定值集的每个ObservablePoint值向左移动,实时绘制一个正弦图。所有建议的性能调整都是适用的,在绘制速率为1/10 of (100 Of)时,结果是可接受的平滑度。您可以在1/50 is到1/200 is之间使用值,甚至可以在1/10 is以下,如果这仍然可以接受的话。

注意,默认的Windows计时器的分辨率为15.6ms。这意味着当鼠标移动时,值<1/100 is将导致呈现停止。设备输入具有优先级,将使用相同的定时器进行处理。您需要找到绘图速率,这为框架处理UI输入留下了足够的时间。

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

DataModel.cs

代码语言:javascript
运行
复制
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));
}

MainWIndow.xaml

代码语言:javascript
运行
复制
<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>
票数 11
EN
查看全部 2 条回答
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/63138397

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档