WPF .NET6アプリでリアルタイムに更新する折れ線グラフを作ろう
時間の経過とともに変化するデータをグラフで表現してみませんか? InfragisticsのXamDataChartを使用すれば、そのようなグラフも簡単に作成することができます。 トライアル版のNuGetパッケージを使用しますので、Infragistics製品の事前インストールは必要ありません。 必要なものはVisualStudio2022だけです。さあ、始めましょう。
Visual StudioでWPFアプリケーションを新規作成する
Visual Studio 2022を開き、「新しいプロジェクトの作成」を選択します。

テンプレートは「WPFアプリケーション」を選択し、「次へ」をクリックします。

適当なプロジェクト名を付け、「次へ」をクリックします。

フレームワークは「.NET6(長期的なサポート)」を選択して「作成」をクリックします。

NuGetパッケージをインストールする
プロジェクトの生成が完了したら、XamDataChartを使用するためのNuGetパッケージをインストールします。 Visual Studioの上部のメニューから「ツール」>「NuGetパッケージマネージャー」>「ソリューションのNuGetパッケージの管理」を選択します。

NuGetパッケージの管理画面で、以下のスクリーンショットのように、 ① パッケージソースに「nuget.org」を選択、② 「参照」タブをクリック、③ 検索ボックスに「Infragistics.WPF.Charts」と入力、④ 表示された「Infragistics.WPF.Charts.Trial」をクリックして選択、⑤ 現在のプロジェクトをチェック、⑥ 「インストール」ボタンをクリックします。

変更のプレビュー画面が表示されたら、「OK」ボタンをクリックします。

以上の操作により、NuGetパッケージInfragistics.WPF.Charts.Trialがプロジェクトにインストールされ、XamDataChartを使用するための準備が整いました。
データの定義
インストールが完了したら、「NuGet – ソリューション」のタブを閉じ、MainWindow.xaml.csを開き、コードビハインドでグラフにバインドするデータを作成します。 まずはデータポイントを表すMyDataPointクラスの定義です。時間軸であるX軸を表すDateTimeタイプのMyDateプロパティと、数値軸であるY軸を表すdoubleタイプのMyValueプロパティを追加しました。
public class MyDataPoint
{
public DateTime MyDate { get; set; }
public double MyValue { get; set; }
}
次に、グラフにバインドするためのデータコレクションを保持するChartDataSourceクラスを用意します。こちらはMyDataPoint タイプのObservableCollectionとなっています。
public class ChartDataSource : ObservableCollection<MyDataPoint>
{
private Random _rand = new Random();
private float _baseVal = 50;
DateTime dt = DateTime.Today;
public ChartDataSource()
{
addDataItem();
}
public void addDataItem()
{
if (_rand.NextDouble() > .5)
{
_baseVal += _rand.Next(0, 5);
if (_baseVal > 100)
{ _baseVal = 100; }
}
else
{
_baseVal -= _rand.Next(0, 5);
if (_baseVal < 0)
{ _baseVal = 0; }
}
this.Add(new MyDataPoint { MyDate = dt, MyValue = _baseVal });
dt = dt.AddSeconds(5);
}
}
ChartDataSourceクラスは自身のコレクションにMyDataPointインスタンスを追加するaddDataItem()メソッドを持ちます。 MyDataPointインスタンスは生成時にMyDate プロパティにはDateTime.Todayを起点として5秒刻みの日付値を、MyValueプロパティには都度ランダムの数値を生成して割り当てます。
MainWindowのコンストラクタでChartDataSourceをインスタンス化し、MainWindowのDataContextに割り当てます。
private ChartDataSource _data;
public MainWindow()
{
InitializeComponent();
_data = new ChartDataSource();
this.DataContext = _data;
}
以上でデータクラスの準備は整いました。
XamDataChartを配置する
次に、MainWindow.xamlを開いてXamDataChartのコーディングを始めます。 まず、Infragisticsのコントロールを参照するためのネームスペースをWindowに追加します。
<Window
…..
xmlns:ig="http://schemas.infragistics.com/xaml">
…..
</Window>
次に、WindowにXamDataChartを配置していきます。
<Window…..>
<Grid>
<ig:XamDataChart x:Name="xamDataChart1" Margin="25">
</ig:XamDataChart>
</Grid>
</Window>
ここでは軸ラベルの見切れを回避するため、Marginプロパティのみ”25”に設定しています。
次に、軸の定義を行います。Y軸は数値軸となるためNumericYAxisを、X軸は時間軸となるためTimeXAxisを定義します。それぞれの定義をXamDataChart.Axesコレクションに追加します。
<ig:XamDataChart.Axes>
<ig:TimeXAxis x:Name="xAxis" ItemsSource="{Binding}" DateTimeMemberPath="MyDate" >
<ig:TimeXAxis.Intervals>
<ig:TimeAxisInterval
Range="0.00:30:00"
Interval="10"
IntervalType="Minutes" />
</ig:TimeXAxis.Intervals>
<ig:TimeXAxis.LabelFormats>
<ig:TimeAxisLabelFormat
Format="HH:mm:ss"
Range="0.00:00:00"/>
</ig:TimeXAxis.LabelFormats>
</ig:TimeXAxis>
<ig:NumericYAxis x:Name="yAxis" MinimumValue="0" MaximumValue="100"/>
</ig:XamDataChart.Axes>
TimeXAxisの実装のポイントはItemsSourceプロパティにDataContextをバインドしていること、そしてDateTimeMemberPathプロパティに先ほどMyDataPointクラスで定義したMyDateプロパティを指定していることです。また、IntervalsおよびLabelFormatsコレクションには今回のグラフで使用する範囲のみ、軸インターバルとラベル書式設定を行っています。 NumericYAxisでは軸の最小値(MinimumValue)と最大値(MaximumValue)のみ指定しています。
次にXamDataChart.Seriesコレクションを定義します。今回のチャートは折れ線グラフのため、LineSeriesを使用します。LineSeriesのインスタンスをXamDataChart.Seriesコレクションに追加します。
<ig:XamDataChart.Series>
<ig:LineSeries
XAxis="{Binding ElementName=xAxis}"
YAxis="{Binding ElementName=yAxis}"
ValueMemberPath="Value"
ItemsSource="{Binding}"
MarkerType="None"/>
</ig:XamDataChart.Series>
XAxisプロパティとYAxisプロパティにはそれぞれ上記で定義したTimeXAxis とNumericYAxisを指定しています。また、ItemsSource にはTimeXAxisの時と同様、DataContextをバインドし、ValueMemberPathにはMyDataPointクラスで定義したMyValueプロパティを指定しています。今回はマーカー表示は行いませんので、MarkerTypeプロパティはNoneとします。
XamDataChartの全体像は以下のようになります。
<ig:XamDataChart x:Name="xamDataChart1" Margin="25">
<ig:XamDataChart.Axes>
<ig:TimeXAxis x:Name="xAxis" ItemsSource="{Binding}" DateTimeMemberPath="MyDate" >
<ig:TimeXAxis.Intervals>
<ig:TimeAxisInterval
Range="0.00:30:00"
Interval="10"
IntervalType="Minutes" />
</ig:TimeXAxis.Intervals>
<ig:TimeXAxis.LabelFormats>
<ig:TimeAxisLabelFormat
Format="HH:mm:ss"
Range="0.00:00:00"/>
</ig:TimeXAxis.LabelFormats>
</ig:TimeXAxis>
<ig:NumericYAxis x:Name="yAxis" MinimumValue="0" MaximumValue="100"/>
</ig:XamDataChart.Axes>
<ig:XamDataChart.Series>
<ig:LineSeries
XAxis="{Binding ElementName=xAxis}"
YAxis="{Binding ElementName=yAxis}"
ValueMemberPath="MyValue"
ItemsSource="{Binding}"
MarkerType="None"/>
</ig:XamDataChart.Series>
</ig:XamDataChart>
MainWindow.xamlの定義は以上です。
リアルタイムのデータフィードを処理する
ここで、リアルタイムでデータのフィードを行うためのDispatcherTimerの実装をコードビハインドに追加していきます。 WindowのLoadedイベントを以下のように実装します。1秒間隔でDispatcherTimerのTickイベントを発行する内容です。
private void Window_Loaded(object sender, RoutedEventArgs e)
{
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromMilliseconds(1000);
timer.Tick += Timer_Tick;
timer.Start();
}
そして、Tickイベントは以下のように実装します。
private void Timer_Tick(object? sender, EventArgs e)
{
for (int i = 0; i < 120; i++)
{
_data.addDataItem();
}
if (_data.Count > 840)
{
xAxis.MinimumValue = xAxis.ActualMinimumValue.AddSeconds(600);
xAxis.MaximumValue = xAxis.ActualMaximumValue.AddSeconds(600);
for (int i = 0; i < 120; i++)
{
_data.RemoveAt(0);
}
}
}
1秒間隔で実行されるTimer_Tickで、グラフのデータコレクションに120ずつデータポイントを追加します。データポイントは5秒刻みで追加していますので、120だと120×5(秒)=600(秒)で10分間分のデータとなりますね。データポイントの総数が840を超えた場合(840×5(秒)=4200(秒)で、70分ぶんのデータポイントがたまった場合)、TimeXAxisのMinimumValueおよび MaximumValue のそれぞれの現在の値に600秒を追加し、グラフが左に流れるようにします。 また、その際にデータポイントを古い方から120削除します。
最後になりましたが、初期ロード時にTimeXAxisの表示範囲が60分となるよう、MaximumValueプロパティにDateTime.Today.AddMinutes(60) を設定します。 これはMainWindowのコンストラクタに追加すればよいでしょう。
public MainWindow()
{
InitializeComponent();
…..
xAxis.MaximumValue = DateTime.Today.AddMinutes(60);
}
実装は以上です。
本記事のサンプルはこちらからダウンロードいただけます。