インフラジスティックス・ジャパン株式会社Blog

インフラジスティックス・ジャパン株式会社のチームメンバーが技術トレンド、製品Tips、サポート情報からライセンス、日々の業務から感じることなど、さまざまなトピックについてお伝えするBlogです。

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);
        }

実装は以上です。

本記事のサンプルはこちらからダウンロードいただけます。