OxyPlotの使い方 ScatterPoint & PolarChart
WPFでChartグラフを表示するためのライブラリーに OxyPlot があります。OxyPlot には 散布図用に ScatterPoint があり、点ごとにサイズと色が指定できます。要素数制限付きの ObservableCollection と合わせて、リアルタイム表示用に残像的なイメージのアニメーションサンプルを作ってみました。
- OxyPlot アプリケーションの基本構成
- 要素数制限付きの ScatterPointCollection
- ScatterPointCollection と DispatcherTimer の追加
- View の作成
- LinerColorAxis の設定
- ScatterSeries の設定
- Plot v.s. PlotView & PlotModel
- 作成したソースコードの場所
OxyPlot の基本はこれを参考にしてください。
OxyPlot アプリケーションの基本構成
Chart は Blend もしくは VisualStudio の XAMLデザイナーで作成して、ViewModel の座標データとバインドする事により、point を追加すると Chart の更新が自動で行われる様にしました。
基本的な構成は次の様になっています。
Model
- 要素数制限付きの ScatterPointCollection クラスを定義
ViewModel
- ScatterPointCollection ScatterPoints を作成
- DispatcherTimer で一定時間ごとに ScatterPoint を追加する為の設定
- DispatcherTimer の起動/停止用の StartFlag プロパティーを設定
View
View にPlotを貼り付ける
Plot の PlotAreaBorderThickness を 0 にして枠線を消す
Plot の PlotType を Polar にするPlot の Axes に 軸を追加
MagnitudeAxis:PolarChartの中心からの距離軸
AngleAxis:PolarChartの角度軸
LinearColorAxis:ScatterPoint の Value の値に応じて色を付ける為の軸Plot の Series に ScatterSeries を追加
ScatterSeries の ItemSource に ViewModel の ScatterPoints をバインドView に Checkbox を貼り付けて、ViewModel の StartFlag とバインド
要素数制限付きの ScatterPointCollection
ObservableCollection を継承して ScatterPoint 用の ScatterPointCollection を作成します。
ScatterPointCollection に
要素数の上限用のプロパティ Limit と Size
上限と下限用に SizeMax, SizeMin,
Value の上限と下限用に ValueMax, ValueMin
を追加します。
Size と Value は、要素の順番で変化させます。
public class ScatterPointCollection : ObservableCollection<ScatterPoint> { protected bool SetProperty<T>(ref T field, T value, [CallerMemberName]string propertyName = null) { if (EqualityComparer<T>.Default.Equals(field, value)) return false; field = value; OnPropertyChanged(new PropertyChangedEventArgs(propertyName)); return true; } protected virtual void Shrink(int index) { while (Limit < Count) RemoveAt(0); for(int i = 0;i < Count; i++) { var item = Items[i]; item.Size = (SizeMax - SizeMin) * (i + 1) / Count + SizeMin; item.Value = (ValueMax-ValueMin)*(i+1)/(double)Count+ValueMin; Items[i] = item; } } private double sizeMax = 1.0; public double SizeMax { get { return sizeMax; } set { SetProperty(ref sizeMax, value); } } private double sizeMin = 1.0; public double SizeMin { get { return sizeMin; } set { SetProperty(ref sizeMin, value); } } private double valueMax = 1.0; public double ValueMax { get { return valueMax; } set { SetProperty(ref valueMax, value); } } private double valueMin = 0.0; public double ValueMin { get { return valueMin; } set { SetProperty(ref valueMin, value); } } private int limit = 50; public int Limit { get { return limit; } set { if (value < 1) value = 1; if (SetProperty(ref limit, value)) { Shrink(Count); } } } protected override void InsertItem(int index, ScatterPoint item) { base.InsertItem(index, item); Shrink(index); } }
ScatterPointCollection と DispatcherTimer の追加
ViewModel に ScatterPoints を追加します。
ScatterPoints は ObservableCollection を継承しているので、UIスレッド以外からは操作できません。
なので、Timer は DispatcherTimer を使用してUIスレッドからPointを追加するようにします。
public MainWindowViewModel() { ScatterPoints.Limit = 50; ScatterPoints.SizeMax = 5.0; ScatterPoints.SizeMin = 1.0; timer.Interval = TimeSpan.FromMilliseconds(25) ; timer.Tick += new EventHandler(MovingPoint); } private DispatcherTimer timer = new DispatcherTimer(); private ScatterPointCollection scatterPoints = new ScatterPointCollection(); public ScatterPointCollection ScatterPoints { get { return scatterPoints; } set { SetProperty(ref scatterPoints, value); } } private double dA1 = 10.0; private double dA2 = 0.7 / 180.0 * Math.PI; private double ang1 = 0.0; private double ang2 = 0.0; /// <summary> /// DispatcherTimerで実行するポイント追加メソッド /// UIスレッドで実行される /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void MovingPoint(object sender, EventArgs e) { double mag = 50.0 + 40.0 * Math.Sin(ang2); double ang = ang1; var p = new ScatterPoint(mag, ang); ScatterPoints.Add(p); ang1 += dA1; ang2 += dA2; }
StartFlag で DispatcherTimer の On/Off を切り替えます。
DispatcherTimer が起動すると メソッド MovingPoint によって ScatterPoint が追加されます。
private bool startFlag = false; /// <summary> /// DispatcherTimerのStart/Stop /// </summary> public bool StartFlag { get { return startFlag; } set { if (value == true) timer.Start(); else timer.Stop(); SetProperty(ref startFlag, value); } }
View の作成
Plot を貼り付け PlotType を Polar にします。
PlotAreaBorderThickness を 0 にして枠線を消しておきます。
次に OxyPlot の プロパティの Axes をクリックして軸を追加します。
OxyPlot の軸には次のようなものがあります。
この中から MagnitudeAxis と AngleAxis と LinearColorAxis を追加します。
それぞれの軸のMaximum, Minimum、Gridの設定を行います。
LinerColorAxis の設定
LinerColorAxis は ScatterPoint の Value の値に応じて色を付けます。
色は LinearColorAxis の GradientStops の設定で行います。
プロパティの GradientStops をクリックし GradientStop を追加します。
GradientStop は 0-100%の間で3点以上必要みたいです。
色の階調は PalletSize で指定します。
ScatterSeries の設定
Plot の Series に ScatterSeries を追加し、ItemSource に ViewModel の ScatterPoints をバインドします。MarkerType を Circle にします。
あとは、View に Checkbox を貼り付けて、ViewModel の StartFlag で DispacherTimer の起動と停止ができるようにすると、サンプルアプリの完成です。
Plot v.s. PlotView & PlotModel
OxyPlot を使用する場合、View に Plot を貼りつけて XAML で Chart を作成して ViewModel のプロパティとバインドする方法と、View に PlotView を貼りつけて表示用の器だけ用意し、ViewModel にPlotModel を作り、コードで Chart を作成する方法の2通りがありますが、その特徴をまとめておきます。
【ViewにPlotを貼りつける場合】
- Chart 本体は View 側の Plot
- 軸、表示する線の定義を XAML で行う
XAMLエディターのプロパティーを修正しながらリアルタイムで Chart が確認できるので初心者向き。動的に線を増やしたりできないので、予め多めに作って IsVisible で隠したりする必要がある。 - ViewModel の点座標用のプロパティーを Plot の Series にバインドする
- Chartの更新はプロパティーの変更通知でできる ObservableCollection を使用すると、点を追加するたびに自動更新される
【View に PlotView を貼りつける場合】
- Chart 本体は ViewModel 側の PlotModel
- 軸とか表示する線の定義は ViewModel のコードで行う。 慣れている人はコードの方が XAML より簡単にかけるが、確認のたびにビルドしないといけない。コードで記述するので、動的に線を増やしたりできる。
- ViewModel の plotModel を PlotView にバインドする
- Chartの更新は PlotModel の InvalidatePlot(true) で行う
作成したソースコードの場所
ソースコードは次の場所に置いてあります。
*参考 - View から ViewModel にBlendのダイアログで選択してバインドするためには、View のデザイン時の DataContext にViewModel が設定されていることが必要です。 (メニューの [形式]-[デザイン時のDataContextの設定] から設定できます)