OxyPlotの使い方 表示の高速化

WPF で Chart グラフを表示するためのライブラリーに OxyPlot があります。リアルタイム系の Chart を表示しようとすると、どうしても表示の遅さが気になります。高速表示する為にはどうしたらいいかを考えてみました。

OxyPlot の基本はこれを参考にしてください。

feynman.hatenablog.com

高速表示の為に注意する事

OxyPlot の Document を見ると次の様に書かれています。

Data binding

How you add data to your model is important for the performance. For series based on the DataPointSeries you have the following options, from fast to slow:

  1. Add instances based on IDataPoint directly to the Points collection
  2. Set ItemsSource to a collection of IDataPoints
  3. Set ItemsSource and use the Mapping delegate
  4. Set ItemsSource and use the data field properties (this uses reflection - slow!)

Style

The following style properties are important for the performance of the rendering:

  1. Solid lines are much faster than dashed/dotted lines
  2. Grid lines are slow to draw (be careful not creating too many of them)
  3. Unfilled markers (Plus, Cross, Star) are faster than the filled markers (Circle, Square, Diamond, Triangle)
  4. Square markers are faster than circles

この中で GridLine の表示による負荷に関して見てみたいと思います。

OxyPlot サンプルソフトでのフレームレート測定

OxyPlot のソースコードをダウンロードして、その中のサンプルソフト OxyPlot.WPF Example Browser を見てみると、次のように表示の負荷が計測できるようなコードが組み込まれています。

/// <summary>
/// The watch.
/// </summary>
private Stopwatch watch = new Stopwatch();

/// <summary>
/// Initializes a new instance of the <see cref="MainWindow" /> class.
/// </summary>
public MainWindow()
{
    this.InitializeComponent();
    this.DataContext = this.vm;
    CompositionTarget.Rendering += this.CompositionTargetRendering;
    this.watch.Start();
}

/// <summary>
/// Handles the Rendering event of the CompositionTarget control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.EventArgs" /> instance containing the event data.</param>
private void CompositionTargetRendering(object sender, EventArgs e)
{
    this.frameCount++;
    if (this.watch.ElapsedMilliseconds > 1000 && this.frameCount > 1)
    {
        this.vm.FrameRate = this.frameCount / (this.watch.ElapsedMilliseconds * 0.001);
        this.frameCount = 0;
        this.watch.Reset();
        this.watch.Start();
    }

    if (this.vm.MeasureFrameRate)
    {
        this.Plot1.InvalidatePlot(true);
    }
}

OxyPlot.WPF Example Browser で、GridLine 有り無しでのフレームレートを比較してみると 30fps と 60fps で2倍の違いがあることが分かります。FrameRate はアプリ画面の左下に表示されています。

WPFレンダリングは60fpsが標準なので、非力なPCだともっと差が出ると思います。

f:id:feynman911:20191017191558j:plain

f:id:feynman911:20191017191543j:plain

Chartの重ね合わせによる高速化

GridLine の表示負荷が思った以上にあるので、リアルタイムで変化する Chart の描画には GridLine は表示しない方がよさそうです。しかしながら、OxyPlotを使用する目的の一つには、美しい Chart を描きたいということがあるはずなので、何もなしにしてしまうのは寂しい。

そこで、Plot を2つ重ねて表示し、下の Plot には GridLine を表示し、上の Plot には GridLine 無しで、リアルタイムで変化する Chart を表示するという細工をしてみました。上の Plot は Background を透過にして、下の GridLine が透けて見えるようにします。100%透過にしてしまうと、マウスイベントを上の Plot で拾えなくなってしまうので、マウスイベントが必要な時には少し残しておきましょう。ベースとなるソフトは、前回書いたものです。

feynman.hatenablog.com

GridLineの表示を抑止するためには、GridLinestyle を None にしています。

どれぐらいの違いが出るかを見るために、上記の CompositionTarget.Rendering を用いた FrameRate の計測を FrameRate として表示しています。ただし、今回のソフトはPlotView ではなく Plot を使用しているので、InvalidatePlot は消しています。代わりに、1秒間に何点 ScatterPointCollection に加えられたかを計測し、PointAddRate として表示しています。

GridLine On/Off のチェックボックスで、上の Plot のGridLineStyle を None にしたり、Solid & Dash にしたり切り替えられるようにしています。

Timer Interval は Point を Add する為の DispatcherTimer の Interval になります。 私のPC環境では25msぐらいよりも長いと、GridLine 有り無しでの違いが分からなくなります。Interval が 25ms の時に PointAddRate は大体 30 ぐらいになりました。

Timer Interval を 0 に設定して、最高速で動かした時には下の様になりました。GridLine を消去することで 2倍ぐらい速く Chart を動かすことができました。

上の Chart のGridLine は、On/Off が分かるように、下の Char よりも若干濃い色にしてあります。

f:id:feynman911:20191018185331j:plain

f:id:feynman911:20191018185346j:plain

まとめ

GridLine の表示は、思った以上に負荷が大きいようです。データが変化しない Chart の時には問題ないですが、動くものを表示したい時には GridLine は表示しない方が良いでしょう。

軸のメモリが固定で良い時には、GridLine 表示のみの静的な Chart を下に敷いて、その上に動的に動く点なり線なりの Chart を重ねることで速い動きが可能となります。

作成したソースコードの場所

ソースコードは次の場所に置いてあります。

github.com