60歳からの電子工作ノート

生涯学習として取り組んでいます。

ScottPlot 4.1とWPF(.NET Framewok)による受信データのリアルタイムグラフ表示

概要

リアルタイムで受信データをグラフ表示するプログラムを、ScottPlot 4.1とC# WPF(.NET Framewok)により作成しました。
Scatter plot(散布図)を使用しています。x軸はデータの受信時刻、y軸は受信データとなります。1秒間隔で、USBシリアル変換ICにデータを送信し、エコーバックしたデータを受信しグラフにしています。

f:id:vABC:20211117154656p:plain
Fig 外観
処理概要

・ボタンとチェックボックス
 Serial Port: USBシリアル通信ポートの設定用ダイアログを表示します。
 Start: 定周期送信タイマー(1秒)を起動して、1秒間隔で2バイトのデータを送信します。
Stop: 定周期送信タイマを停止して、送信を終了します。
 Clear: 送受信情報表示をクリアします。
Auto Axis: X軸、Y軸の範囲をグラフが表示される範囲に再設定します。(マウスのMiddle-clickでも可能です。)
PV(ch1): trend_data0 のグラフの表示/非表示を選択します。
SV:trend_data1のグラフの表示/非表示を選択します。
 
・Scatter Plot(散布図)の表示
散布図を使用する場合にはdouble型のX軸用の配列データとY軸用の配列データを用意します。AddScatterによりグラフが表示されます。

f:id:vABC:20211118051900p:plain
Fig 散布図とデータ

・データ送信/受信処理
8月の「シリアル通信プログラムによるCH340Eの動作確認」(https://vabc.hatenadiary.jp/entry/2021/08/29/175717 )と同様です。ここでは、0~9までの値と10(0x0A)の値を1秒毎に2バイトを送信しています。また受信監視タイマ(送信から200msec以内に受信しないとTime Out)が追加されています。
グラフに最新の受信データを表示するため、受信時に配列の一つ後のデータを一つ前に移動させ、配列の最後に受信データを格納します。その後グラフを更新します。

f:id:vABC:20211120140846p:plain
Fig データ受信時の配列データの移動(trend_data0の場合)

・通信ポートの設定ダイアログ
通信ポートの設定は、メインウィンドウとは別にダイアログにしています。

f:id:vABC:20211120162207p:plain
Fig 通信ポート設定ダイアログ

このダイアログは次の手順で、WPFウィンドウを追加して作成します。
プロジェクト名(本例では PlotTest)を右クリックして「追加」を選択。その後「ウィンドウ(WPF)」を選択すると、新しい xaml(例:ConfSerial)が追加できます。

f:id:vABC:20211120144313p:plain
Fig WPFウィンドウの追加 1
f:id:vABC:20211120145854p:plain
Fig WPFウィンドウの追加 2
プログラム

ScottPlotを使用するためには、NuGetによるインストールが必要です。新規ソリューション(例:PlotTest)作成後、NuGetによりScottPlot.WPFをインストールします。
・メインウィンドウ XAML部分 (MainWindow.xaml) 

<Window x:Class="PlotTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:PlotTest"
        mc:Ignorable="d"
        ResizeMode="CanResizeWithGrip" 
        Title="PlotTest" Height="600" Width="800" FontSize="14" FontFamily="MS UI Gothic"  Background="White" Foreground="#333333">
    <Grid ShowGridLines="False">
        <!-- Gridで使用するボタンの大きさ、色を定義-->
        <Grid.Resources>
            <Style TargetType="Button">
                <Setter Property="Height" Value="30" />
                <Setter Property="Width" Value="100"/>
                <Setter Property="Margin" Value="10" />
                <Setter Property="BorderBrush" Value="#a6a6a6" />
                <Setter Property="Foreground" Value="#333333" />
                <Setter Property="Background" Value="#fcfcfc"/>
            </Style>
        </Grid.Resources>

        <!-- カラム Grid 横方向の大きさ指定。 "AUTO"は、横幅を変更するGridSplitterの部分  -->
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*"  MinWidth="100"/>
            <ColumnDefinition Width="AUTO"/>
            <ColumnDefinition Width="2*" MinWidth="100" />
            <ColumnDefinition Width="0.6*" MinWidth="100" />
        </Grid.ColumnDefinitions>

        <!-- Grid 行方向の大きさ指定 "AUTO"は、高さを変更する GridSplitterの部分-->
        <Grid.RowDefinitions>
            <RowDefinition Height="4*"  MinHeight="100" />
            <RowDefinition Height="AUTO"  />
            <RowDefinition Height="1*" MinHeight="100" />
        </Grid.RowDefinitions>

        <!--横幅を変更する GridSplitter-->
        <GridSplitter Grid.Row="0" Grid.Column="1"   Grid.RowSpan="3" Width="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Gainsboro"/>

        <!--高さを変更する GridSplitter-->
        <GridSplitter Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="4" Height="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Gainsboro"/>


        <!-- スタックパネル (row=0,col=0) 送信ボタンを配置-->
        <!-- row= 1 には、Gridsplitterが配置されている -->
        <StackPanel Grid.Row="0" Grid.Column="0"  Margin="10">
           
            <StackPanel Orientation="Vertical" Margin="0,20,0,0">
               
                <Button Content="Serial Port"  Click="Serial_Button_Click"/>
                <Button Content="Start"  Click="Start_Button_Click"/>
                <Button Content="Stop"  Click="Stop_Button_Click"/>
                <Button Content="Clear" Click="Clear_Button_Click" />

            </StackPanel>
            <StackPanel Orientation="Horizontal" Margin="0,20,0,0">
                <TextBlock Text="Status:"/>
                <TextBlock x:Name="StatusTextBlock"  Margin ="10,0,0,0" Text =""/>
            </StackPanel>
        </StackPanel>

        <WpfPlot Name="wpfPlot_PV"  Grid.Row="0" Grid.Column="2" />
        <!-- ドックパネル (row=0,col=2) TextBlockを配置-->
        <!-- col= 1 には、Gridsplitterが配置されている -->
        <DockPanel Grid.Row="2"   Grid.Column="0" Margin="10">
            <TextBlock  DockPanel.Dock="Top"   Text="Send/Receive  Info."/>
            <ScrollViewer x:Name ="LogTextScroll" VerticalScrollBarVisibility="Auto">
                <TextBlock x:Name="SendRcvTextBlock"  Margin ="10" Text ="" />
            </ScrollViewer>
        </DockPanel>

        <!-- スタックパネル (row=0,col=3) チェックボックスを配置-->
        <StackPanel Grid.Row="0" Grid.Column="3"  Margin="10">
            <StackPanel Orientation="Horizontal">
                <CheckBox x:Name="PV_CheckBox" Margin="8" IsChecked="True"  Checked="PV_X_Show" Unchecked="PV_X_Hide"/>
                <Label  Content ="PV(ch1)" Margin="0,4,0,0"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <CheckBox x:Name="SV_CheckBox" Margin="8" IsChecked="True"  Checked="PV_X_Show" Unchecked="PV_X_Hide"/>
                <Label  Content ="SV" Margin="0,4,0,0"/>
            </StackPanel>
            <Button Content="Axis Auto" Margin="0,40,0,0" Click="Axis_Auto_Button_Click" />
        </StackPanel>
    </Grid>
</Window>


・メインウィンドウ C#部分 (MainWindow.xaml.cs) 

using ScottPlot;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO.Ports;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace PlotTest
{


    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    /// 
    public partial class MainWindow : Window
    {
      

        byte[] sendBuf;          // 送信バッファ   
        int sendByteLen;         // 送信データのバイト数

        byte[] rcvBuf;           // 受信バッファ
        int srcv_pt;             // 受信データ格納位置

        int data_receive_thread_id;  // データ受信ハンドラのスレッドID
        int data_receive_thread_cnt;  // データ受信ハンドラの実施回数

        DispatcherTimer SendIntervalTimer; // タイマー モニタ用 電文送信間隔   
        DispatcherTimer RcvWaitTimer;       // タイマー 受信待ち用 

        UInt32 msg_num;         // メッセージ番号

                                // リアルタイム PV,MVグラフ用 
        uint trend_data_item_max;             // 各リアルタイム トレンドデータの保持数(=10 ) 2秒毎に収集すると、20秒分のデータ

        double[] trend_data0;                 // トレンドデータ 0   PV(ch1)
        double[] trend_data1;                 // トレンドデータ 1   SV

        double[] trend_dt;                    // トレンドデータ 収集日時

        byte[] send_data0;
        byte[] send_data1;


        ScottPlot.Plottable.ScatterPlot trend_signal_0; // トレンドデータ0 
        ScottPlot.Plottable.ScatterPlot trend_signal_1; // トレンドデータ1

        DateTime receiveDateTime;           // 受信完了日時


        public MainWindow()
        {
            InitializeComponent();

            ConfSerial.serialPort = new SerialPort();    // シリアルポートのインスタンス生成
            ConfSerial.serialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);  // データ受信時のイベント処理


            sendBuf = new byte[2048];     // 送信バッファ領域  serialPortのWriteBufferSize =2048 byte(デフォルト)
            rcvBuf = new byte[4096];      // 受信バッファ領域   SerialPort.ReadBufferSize = 4096 byte (デフォルト

            SendIntervalTimer = new System.Windows.Threading.DispatcherTimer();  // タイマーの生成
            SendIntervalTimer.Tick += new EventHandler(SendIntervalTimer_Tick);  // タイマーイベント
            SendIntervalTimer.Interval = new TimeSpan(0, 0, 0, 0, 1000);         // タイマーイベント発生間隔 1sec(送信間隔)

            RcvWaitTimer = new System.Windows.Threading.DispatcherTimer();  // タイマーの生成(受信待ちタイマ)
            RcvWaitTimer.Tick += new EventHandler(RcvWaitTimer_Tick);        // タイマーイベント
            RcvWaitTimer.Interval = new TimeSpan(0, 0, 0, 0, 200);          // タイマーイベント発生間隔 (受信待ち時間)


            trend_data_item_max = 10;             // 各リアルタイム トレンドデータの保持数(=10 )

            Chart_Ini();                        // チャート(リアルタイム用)の初期化


            send_data0 = new byte[trend_data_item_max];
            send_data1 = new byte[trend_data_item_max];

            for (int i = 0; i < trend_data_item_max; i++)               // 送信データの作成
            {
                send_data0[i] = (byte) i;
                send_data1[i] = (byte) trend_data_item_max;
            }

           
            

    }



        //  チャートの初期化(リアルタイム チャート用)
        //    
        private void Chart_Ini()
        {
         
            trend_data0 = new double[trend_data_item_max];
            trend_data1 = new double[trend_data_item_max];

            trend_dt = new double[trend_data_item_max];


            DateTime datetime = DateTime.Now;   // 現在の日時

            DateTime[] myDates = new DateTime[trend_data_item_max];

            for (int i = 0; i < trend_data_item_max; i++)
            {
                trend_data0[i] =  i;
                trend_data1[i] = trend_data_item_max;

                myDates[i] = datetime + new TimeSpan(0, 0, i);  // i秒増やす

                trend_dt[i] = myDates[i].ToOADate();   // (現在の日時 + i 秒)をdouble型に変換
            }


            wpfPlot_PV.Refresh();       // データ変更後のリフレッシュ
           

            trend_signal_0 = wpfPlot_PV.Plot.AddScatter(trend_dt, trend_data0, color: System.Drawing.Color.Orange, label: "PV(ch1)"); // プロット plot the data array only once
            trend_signal_1 = wpfPlot_PV.Plot.AddScatter(trend_dt, trend_data1, color: System.Drawing.Color.Green, label: "SV");
          

            // PVグラフ
            wpfPlot_PV.Configuration.Pan = true;               // パン(グラフの移動)可
            wpfPlot_PV.Configuration.ScrollWheelZoom = true;   // ズーム(グラフの拡大、縮小)可
            
            wpfPlot_PV.Plot.AxisAuto();                         // X軸、Y軸のオートスケール

            wpfPlot_PV.Plot.XAxis.Ticks(true, false, true);         // X軸の大きい目盛り=表示, X軸の小さい目盛り=非表示, X軸の目盛りのラベル=表示
            wpfPlot_PV.Plot.XAxis.TickLabelStyle(fontSize: 14);     //X軸   ラベルのフォントサイズ変更  :
            wpfPlot_PV.Plot.XAxis.TickLabelFormat("HH:mm:ss", dateTimeFormat: true); // X軸 時間の書式(例 12:30:15)、X軸の値は、日時型
            wpfPlot_PV.Plot.XLabel("time");                         // X軸全体のラベル

            wpfPlot_PV.Plot.YAxis.TickLabelStyle(fontSize: 14);     // Y軸   ラベルのフォントサイズ変更  :
            wpfPlot_PV.Plot.YAxis.Label(label: "[℃]", color: System.Drawing.Color.Black);    // Y軸全体のラベル

          

            var legend1 = wpfPlot_PV.Plot.Legend(enable: true, location: Alignment.UpperRight);   // 凡例の表示
        
            legend1.FontSize = 14;      // 凡例のフォントサイズ
        
        }



        //
        // Start ボタン
        //
        private void Start_Button_Click(object sender, RoutedEventArgs e)
        {

            msg_num = 0;            // メッセージ番号の初期化

            SendIntervalTimer.Start();     // 定周期送信用のタイマー開始

        }

        //
        // Stop ボタン
        //
        private void Stop_Button_Click(object sender, RoutedEventArgs e)
        {
            SendIntervalTimer.Stop();     //定周期送信用のタイマー停止

            StatusTextBlock.Text = "Stop";
        }



        // 
        // 定周期にデータを送信する。
        //
        private void SendIntervalTimer_Tick(object sender, EventArgs e)
        {
            Send_TestData();     //    テスト用データの 送信
            
            StatusTextBlock.Text = "";
        }

        //
        //  データの送信
        //
        private void Send_TestData()
        {
            if (ConfSerial.serialPort.IsOpen == true)
            {
                srcv_pt = 0;                   // 受信データ格納位置クリア
                data_receive_thread_cnt = 0;  // 

                sendByteLen = 2;            // 送信バイト数


                sendBuf[0] = send_data0[msg_num];    // 送信データを送信バッファへ格納
                sendBuf[1] = send_data1[msg_num];

                ConfSerial.serialPort.Write(sendBuf, 0, sendByteLen);     // データ送信
                
                RcvWaitTimer.Start();        // 受信監視タイマー 開始


                if ( msg_num < (trend_data_item_max - 1) ){ 
                    msg_num = msg_num + 1;        // メッセージ番号インクリメント
                }
                else
                {
                    msg_num = 0;
                }


                SendRcvTextBlock.Text += "Snd: ";         // 送信の意味

                for (int i = 0; i < sendByteLen; i++)   //  送信データの表示
                {
                    if ((i > 0) && (i % 16 == 0))    // 16バイト毎に1行空ける
                    {
                        SendRcvTextBlock.Text += "\r\n";
                    }

                    SendRcvTextBlock.Text += sendBuf[i].ToString("X2") + " ";

                }
                SendRcvTextBlock.Text += "(" + DateTime.Now.ToString("HH:mm:ss.fff") + ")" + "\r\n";   // 時刻

            }

            else
            {
                StatusTextBlock.Text = "Comm port closed !";

            }

        }




        // 送信後、200msec以内に受信文が得られないと、受信エラー
        //  
        private void RcvWaitTimer_Tick(object sender, EventArgs e)
        {
            int id = System.Threading.Thread.CurrentThread.ManagedThreadId;

            RcvWaitTimer.Stop();        // 受信監視タイマー 停止
            SendIntervalTimer.Stop();     //定周期送信用のタイマー停止

            StatusTextBlock.Text = "Receive time out";
        }



        // デリゲート関数の宣言
        private delegate void DelegateFn();

        // データ受信時のイベント処理
        private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
        {

            int rd_num = ConfSerial.serialPort.BytesToRead;       // 受信データ数

            ConfSerial.serialPort.Read(rcvBuf, srcv_pt, rd_num);   // 受信データを読み出して、受信バッファに格納

            srcv_pt = srcv_pt + rd_num;     // 次回の保存位置
            data_receive_thread_cnt++;      // データ受信ハンドラの実施回数のインクリメント

            int id = System.Threading.Thread.CurrentThread.ManagedThreadId;
            data_receive_thread_id = id;                          //データ受信スレッドのID格納


            if (srcv_pt == sendByteLen)  // 最終データ受信済み (受信データ数は、送信バイト数と同一とする) イベント処理の終了
            {
                RcvWaitTimer.Stop();        // 受信監視タイマー 停止

                receiveDateTime = DateTime.Now;   // 受信完了時刻を得る

                Dispatcher.BeginInvoke(new DelegateFn(RcvProc)); // Delegateを生成して、RcvProcを開始   (表示は別スレッドのため)
            }

        }

        //
        // データ受信イベント終了時の処理
        // 受信データの表示
        //
        private void RcvProc()
        {

            // グラフ(リアルタイム)用表示データの作成
            // グラフを左にずらす (一つ前の配列indexへ移動 )
            Array.Copy(trend_data0, 1, trend_data0, 0, trend_data_item_max - 1);
            trend_data0[trend_data_item_max - 1] = (double)rcvBuf[0];           // 受信した最新データを、グラフ用のデータ配列へ格納

            Array.Copy(trend_data1, 1, trend_data1, 0, trend_data_item_max - 1);
            trend_data1[trend_data_item_max - 1] = (double)rcvBuf[1];

            Array.Copy(trend_dt, 1, trend_dt, 0, trend_data_item_max - 1);
            trend_dt[trend_data_item_max - 1] = receiveDateTime.ToOADate();    // 受信日時 double型に変換して、格納

            wpfPlot_PV.Refresh();       // データ変更後のリフレッシュ
            wpfPlot_PV.Render();        // リアルタイム グラフの更新
            
            wpfPlot_PV.Plot.AxisAuto();   // X軸の範囲を更新

            rcvmsg_disp();                  // 受信データの表示

        }



        // 受信データの表示
        //
        private void rcvmsg_disp()
        {
            string rcv_str = "";

            SendRcvTextBlock.Text += "Rcv: ";      // 受信の意味
            for (int i = 0; i < srcv_pt; i++)   // 表示用の文字列作成
            {
                if ((i > 0) && (i % 16 == 0))    // 16バイト毎に1行空ける
                {
                    rcv_str = rcv_str + "\r\n";
                }

                rcv_str = rcv_str + rcvBuf[i].ToString("X2") + " ";
            }

            SendRcvTextBlock.Text += rcv_str;  // 受信文

            SendRcvTextBlock.Text += "(" + receiveDateTime.ToString("HH:mm:ss.fff") + ")(" + srcv_pt.ToString() + " bytes )" + "\r\n" +"\r\n";

            LogTextScroll.ScrollToBottom();                                // 一番下までスクロール

        }



            // クリアボタンを押した時の処理
            private void Clear_Button_Click(object sender, RoutedEventArgs e)
        {
            msg_num = 0;            // メッセージ番号の初期化

            SendRcvTextBlock.Text = "";

        }

        // 通信ポートの ダイアログを開く
        //  
        private void Serial_Button_Click(object sender, RoutedEventArgs e)
        {
            new ConfSerial().ShowDialog();
        }

        // チェックボックスによる表示
        private void PV_X_Show(object sender, RoutedEventArgs e)
        {

            if (trend_signal_0 is null) return;
            if (trend_signal_1 is null) return;

            CheckBox checkBox = (CheckBox)sender;

            if (checkBox.Name == "PV_CheckBox")
            {
                trend_signal_0.IsVisible = true;
            }
            else if (checkBox.Name == "SV_CheckBox")
            {
                trend_signal_1.IsVisible = true;
            }

            wpfPlot_PV.Render();   // グラフの更新

        }


        // チェックボックスによる非表示
        private void PV_X_Hide(object sender, RoutedEventArgs e)
        {

            if (trend_signal_0 is null) return;
            if (trend_signal_1 is null) return;

            CheckBox checkBox = (CheckBox)sender;

            if (checkBox.Name == "PV_CheckBox")
            {
                trend_signal_0.IsVisible = false;
            }
            else if (checkBox.Name == "SV_CheckBox")
            {
                trend_signal_1.IsVisible = false;
            }

            wpfPlot_PV.Render();   // グラフの更新
        }

        // Axise Auto ボタンを押した時の処理
        // グラフのパン(移動)、ズームで線が見えなくなった時、オートスケールで再表示させる。
      

        private void Axis_Auto_Button_Click(object sender, RoutedEventArgs e)
        {
            wpfPlot_PV.Plot.AxisAuto();         // X軸、Y軸のオートスケール
            wpfPlot_PV.Render();                // グラフの更新
        }
    }
}


・通信ポート設定ダイアログ用 XAML部分 (ConfSerial.xaml) 

Window x:Class="PlotTest.ConfSerial"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:PlotTest"
        mc:Ignorable="d"
        Title="ConfSerial" Height="380" Width="280">
    <Grid>
        <StackPanel Grid.Row="0" Grid.Column="0" >

            <StackPanel.Resources>
                <Style TargetType="Button">
                    <Setter Property="Height" Value="30" />
                    <Setter Property="Width" Value="100"/>
                    <Setter Property="Margin" Value="10" />
                    <Setter Property="BorderBrush" Value="#a6a6a6" />
                    <Setter Property="Foreground" Value="#333333" />
                    <Setter Property="Background" Value="#fcfcfc"/>
                </Style>

            </StackPanel.Resources>


            <GroupBox Header="Serial communication port" Margin="10">
                <StackPanel >
                    <ComboBox  x:Name = "ComPortComboBox"   TextSearch.TextPath="ComPortName" Height="30" Width="100" Margin="10" BorderBrush="#a6a6a6" Foreground="#333333" Background="#fcfcfc">
                        <ComboBox.ItemTemplate>
                            <DataTemplate>
                                <StackPanel Orientation="Horizontal"  >
                                    <TextBlock Text="{Binding ComPortName}"  />
                                </StackPanel>
                            </DataTemplate>
                        </ComboBox.ItemTemplate>
                    </ComboBox>

                    <Button x:Name= "ComPortOpenButton" Content="Open" Click="ComPortOpenButton_Click"/>
                    <Button x:Name= "ComPortSearchButton"  Content="Find"  Click="ComPortSearchButton_Click"/>
                    <TextBlock HorizontalAlignment="Center" Margin="10">
                        ( 76.8 Kbps,1-stop, no-parity)
                    </TextBlock>

                </StackPanel>
            </GroupBox>

            <GroupBox Header="Info." Margin="10">
                <TextBox x:Name="OpenInfoTextBox"   IsReadOnly="True" BorderThickness="0"  Text ="Open/Close infomation."/>
            </GroupBox>

            <Button x:Name= "ConfOK"  Content="OK"  Click="ConfOKButton_Click"/>

        </StackPanel>
    </Grid>
</Window>

・通信ポート設定ダイアログ用 C#部分 (ConfSerial.xaml.cs) 

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace PlotTest
{


    // COMポートの コンボボックス用 
    public class ComPortNameClass
    {
        string _ComPortName;

        public string ComPortName
        {
            get { return _ComPortName; }
            set { _ComPortName = value; }
        }
    }




    /// <summary>
    /// ConfSerial.xaml の相互作用ロジック
    /// </summary>
    public partial class ConfSerial : Window
    {
        public ObservableCollection<ComPortNameClass> ComPortNames;    // 通信ポート(COM1,COM2等)のコレクション 
                                                                       // データバインドするため、ObservableCollection 
        public static SerialPort serialPort;        // シリアルポート


        public ConfSerial()
        {
            InitializeComponent();

            ComPortNames = new ObservableCollection<ComPortNameClass>();  // 通信ポートのコレクション インスタンス生成

            ComPortComboBox.ItemsSource = ComPortNames;       // 通信ポートコンボボックスのアイテムソース指定  

            SetComPortName();                // 通信ポート名をコンボボックスへ設定

            if (ComPortNames.Count > 0)     // 通信ポートがある場合
            {
                if (serialPort.IsOpen == true)    // new Confserial()実行時に、 既に Openしている場合
                {
                    OpenInfoTextBox.Text = "通信ポート(" + serialPort.PortName + ")は、既にオープンしています。";
                    ComPortOpenButton.Content = "Close";      // ボタン表示 Close
                }
                else
                {
                    OpenInfoTextBox.Text = "通信ポート(" + serialPort.PortName + ")は、クローズしています。";
                    ComPortOpenButton.Content = "Open";      // ボタン表示 Close
                }
            }
            else
            {
                OpenInfoTextBox.Text = "通信ポートが見つかりません。";
            }


        }



        // 通信ポート名をコンボボックスへ設定
        private void SetComPortName()
        {
            ComPortNames.Clear();           // 通信ポートのコレクション クリア


            string[] PortList = SerialPort.GetPortNames();              // 存在するシリアルポート名が配列の要素として得られる。

            foreach (string PortName in PortList)
            {
                ComPortNames.Add(new ComPortNameClass { ComPortName = PortName }); // シリアルポート名の配列を、コレクションへコピー
            }

            if (ComPortNames.Count > 0)
            {
                ComPortComboBox.SelectedIndex = 0;   // 最初のポートを選択

                ComPortOpenButton.IsEnabled = true;  // ポートOPENボタンを「有効」にする。
            }
            else
            {
                ComPortOpenButton.IsEnabled = false;  // ポートOPENボタンを「無効」にする。
            }

        }



        // 通信ポートの検索
        //
        private void ComPortSearchButton_Click(object sender, RoutedEventArgs e)
        {
            SetComPortName();
        }


        //
        // 通信ポートのオープン
        //
        //  SerialPort.ReadBufferSize = 4096 byte (デフォルト)
        //             WriteBufferSize =2048 byte
        //
        private void ComPortOpenButton_Click(object sender, RoutedEventArgs e)
        {
            if (serialPort.IsOpen == true)    // 既に Openしている場合
            {
                try
                {
                    serialPort.Close();

                    OpenInfoTextBox.Text = "通信ポート(" + serialPort.PortName + ")を、クローズしました。";

                    ComPortComboBox.IsEnabled = true;        // 通信条件等を選択できるようにする。
                    ComPortSearchButton.IsEnabled = true;    // 通信ポート検索ボタンを有効とする。
                    ComPortOpenButton.Content = "Open";    // ボタン表示を Closeから Openへ

                }
                catch (Exception ex)
                {
                    OpenInfoTextBox.Text = ex.Message;
                }

            }
            else                      // Close状態からOpenする場合
            {
                serialPort.PortName = ComPortComboBox.Text;    // 選択したシリアルポート

                serialPort.BaudRate = 76800;           // ボーレート 76.8[Kbps]
                serialPort.Parity = Parity.None;       // パリティ無し
                serialPort.StopBits = StopBits.One;    //  1 ストップビット

                serialPort.Open();             // シリアルポートをオープンする
                serialPort.DiscardInBuffer();  // 受信バッファのクリア


                ComPortComboBox.IsEnabled = false;        // 通信条件等を選択不可にする。

                ComPortSearchButton.IsEnabled = false;    // 通信ポート検索ボタンを無効とする。

                OpenInfoTextBox.Text = "通信ポート(" + serialPort.PortName + ")を、オープンしました。";

                ComPortOpenButton.Content = "Close";      // ボタン表示を OpenからCloseへ

            }
        }

        // OKボタン
        private void ConfOKButton_Click(object sender, RoutedEventArgs e)
        {
            this.Close();
        }

    }
}
開発環境

ソフト:Win10
Microsoft Visual Studio Community 2022 (64 ビット) - CurrentVersion 17.0.0
C#, .NET Framework 4.8
ScottPlot 4.1.27
ハード:USBシリアル変換IC(CH340E)のRTDとTXDを結線してエコーバックさせています。

ScottPloptについて

サイト(https://scottplot.net/faq/compare/)の情報によればScottPlotの理念は、
・tens of millions のデータが容易に表示できる。
・パン*1とズーム*2がマウスで相互に行え十分に早い
・コンソールアプリまたはサーバ環境での Function
・複雑なオブジェクトを学ぶ必要がなく、
 データは単純にdouble[]に入れ、
 データは1行のコードでグラフ表示できる。
と書かれています。
また、グラフ上でマウスを右クリックするとメニューが表示されグラフのコピーや保存等が可能です。

f:id:vABC:20211121081630p:plain
Fig マウス右クリックによるメニュー

*1:マウス右ボタンを押しながら、グラフを移動させること

*2:マウスのスクロールによるX軸とY軸の拡大縮小