PR

VB.NET(VB2022)のChartでトレンドグラフを表示する

VB.NET
本記事はプロモーションが含まれています。

こんにちは、ENGかぴです。

VB.NET(Visual Basic 2022)のChartを使用するとArduino等の外部機器から通信で取得したデータをグラフで表示することができます。UDP通信を使って取得した温湿度データをトレンドグラフで表示する例をまとめました。

Chartの実装の方法とUDP通信の方法は下記記事にまとめています。ソースコードを流用してトレンドグラフを表示するようにします。

VB.NET(VB2022)のChartでグラフを表示する方法

VB.NET(VB2022)のUDP通信を非同期処理で行う方法

Arduinoは下記記事のESP-WROOM-32E(以下ESP32とする)を使用します。

ESP32-WROOM-32Eで温湿度データをUDP通信する

Windowsフォームアプリケーション(.NETFramework)を対象としています。以下はVisual Basic 2022をVB2022とします。

VB.NET(VB2022)のデスクトップアプリで動作確認したことを下記リンクにまとめています。

VB.NET(VB2022)のデスクトップアプリ開発でできること

Chartでトレンドグラフを表示する

Chartでトレンドグラフを表示するための全体構成
Chartでトレンドグラフを表示するための全体構成

①のESP32はUDPサーバーとして動作し、DHT20モジュールから定期的(10秒毎)に温湿度データを取得しながらクライアントの要求を待機します。

②のクライアントは本記事で作成するアプリになります。アプリからタイマのインターバル(10秒)毎に「CHARTAPP」の文字列をUDP通信でESP32に送信します。

ESP32はアプリからの要求に対して温湿度データを含めた電文で応答します。ESP32からの応答パケットから温湿度のデータを取得してChartのデータに組み込んでグラフ表示します。

クライアントで使用しているパソコンはWiFiを搭載していないためLANケーブルをアクセスポイントに接続して通信します。

スポンサーリンク

コントロールの追加

本記事で作成するアプリの動作画面
本記事で作成するアプリの動作画面

本記事のコントロールは流用元のVB.NET(VB2022)のUDP通信を非同期処理で行う方法で使用しているUDP通信に関わるコントロールにChartのコントロールを追加して実装します。

黒色のコントロールは流用元のUDP通信に関するコントロールです。赤色が追加するコントロールです。Label1~4についてはデフォルトの表示を使用しているため黒色ですが追加する項目です。

Chartを初期配置すると棒グラフの表示になるためChartのSeriesからSeries1メンバーのChartTypeをSpLineに変更(ソースコードで変更できますが見た目の問題でプロパティを直接変更しています)すると丸みを帯びた線グラフの表示になります。

温湿度の変化の傾向を表示するためスケーリング値(範囲:0~10000)を使用してグラフを表示します。カーソル位置のタイムスタンプと温湿度が分かるようにスケーリング値から換算したデータをカーソル欄に表示します。

トレンドグラフに複数の項目がある場合、軸の範囲がデータの大きい方を基準になるため値の範囲が狭い項目の傾向が掴みづらくなります。そのため項目に寄らず一律の基準として0~10000の範囲にスケーリングした値でトレンドグラフを表示します。

2軸にする方法がありますが、それぞれの軸のグリッド線を引くと線が煩雑になり見にくくなるためトレンドグラフのようにデータの傾向を確認する目的であればスケーリング値を使用する方法が良い場合もあります。

PR:RUNTEQ(ランテック )- マイベスト3年連続1位を獲得した実績を持つWebエンジニア養成プログラミングスクール

応答電文からデータを取得する

ESP32の応答電文
ESP32の応答電文

応答電文はヘッダ8バイトに続けて湿度のデータ、温度のデータを続けて合計16バイトのデータになります。Timer1のTickタスクで電文を判定を行います。

Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick

    If sz >= 8 Then
        rp = RxRing.rp
        For i = 0 To 8 - 1
            str &= Chr(RxRing.dat(rp))
            rp += 1
        Next

        If str = "CHARTAPP" Then
            If sz >= 16 Then      
                For i = 0 To 16 - 1
                    logbuf(i) = RxRing.dat(RxRing.rp)
                    RingRpAdd()
                Next
                ChartDataSet()
            End If
        End If
    End If
End Sub

3行目はヘッダーのサイズである8バイト以上であるかの確認を行っています。8バイト以上であれば受信したデータから文字列を生成します。(5行~8行目)Chr()メソッドでアスキー文字に変換して文字列を&で連結しています。

生成した文字列が「CHARTAPP」と一致する場合、すべてのデータを受け入れて取得します。(12行~15行目)16行目のCharDataSet()内でChartにデータをセットします。

Chartにデータをセットする

Private Sub ChartDataSet()    
    Dim tempbuf(GLF_ROW) As Integer
        
    temp = (CInt(logbuf(12)) << 8) + logbuf(13)
    temp_log = (CInt(logbuf(14)) << 8) + logbuf(15)
    log.temp.CopyTo(tempbuf, 1)

    For i = 1 To GLF_ROW - 1
        log.temp(i) = tempbuf(i)
    Next

    log.temp(0) = temp_log
    '以下省略
End Sub

4行目、5行目は温度データ(temp:100倍値)と温度データ(temp_log:スケーリング値)のデータを2バイトのデータに換算しています。スケーリング値をグラフにセットして表示します。

Chartのグラフは軸の左端が最古のデータ、右端が最新のデータになるようにプロットします。グラフ用のデータは最新のデータから降順に古いデータとなり配列の末端が最古のデータになるようにセットします。

6行目のCopyTo()メソッドは配列をコピーします。第1引数にコピー先の配列を指定します。第2引数に先頭からオフセット(ずらして格納)するバイト数を指定します。例ではlog.temp[]配列を1バイトずらしてtempbuf[]配列に格納しています。

コピー元の配列はコピー先の配列よりも範囲が小さくコピー先の配列の範囲を超えないように指定する必要があります。

log.temp[]のサイズを360、tempbuf[]のサイズを361にすることで1バイトずらして格納できるようにしています。

tempbuf[]配列はlog.temp[]配列を1バイトずらした配列になっているため8行~10行目でlog.temp[]配列に格納し直してlog.temp[]配列のデータを1つずらした配列を生成しています。最後に最新のデータを配列0番目にセットすると降順に古いデータとなり配列の末端が最古のデータになります。

Chartにセットするデータを生成します。DataTabelクラスの変数を初期化(New)して準備し、行(Row)と列(Columns)のデータに分けて格納します。

Dim dt As New DataTable
Dim row As DataRow
       
dt.Columns.Add("X")
dt.Columns.Add("Temperature")

For i = 0 To GLF_ROW - 1
    row = dt.NewRow() '行項目を新規追加
    row("X") = log.timstp(GLF_ROW - 1 - i)
    row("Temperature") = log.temp(GLF_ROW - 1 - i)
    dt.Rows.Add(row)
Next

Chart1.Series.Clear() 'グラフの項目をクリア
Chart1.DataSource = dt 'DataTableと関連付け
Chart1.DataBind() 'コントロールををデータソースにバインド

dtオブジェクトのColumnsのAdd()メソッドで列の項目を指定します。例では文字列の”X”を指定しています。続けてAdd()メソッドで文字列の”Temperature”を指定して列の項目を追加します。

列の項目に対して行のデータを追加します。DataRowクラスのrowオブジェクトのNewRow()メソッドで行項目を生成し、列で追加した項目に対応する行のデータをセットします。

row(“X”)はデータを取得したときのタイプスタンプをセットしています。軸のラベル表示には使用しませんがカーソル欄で時刻を確認する時の参照データになります。row(“Temperature”)はlog.temp[]配列を指定します。配列の参照は最古のデータが先頭(8行~9行目のGLF_ROW -1 – i の部分です)になるようにしています。

rowオブジェクトに行のデータをセットした後は、dtオブジェクトのRowsコレクションにAdd()メソッドで行のデータを追加します。7行~12行目を繰り返してグラフのデータをセットします。

ChartのSeriesのコレクションをClear()メソッドで初期化します。Chart1のDataSourceにdtオブジェクトを指定するとグラフのデータとして使用できるようになります。

広告
マイベスト3年連続1位を獲得した実績を持つ実践型のプログラミングスクール

グラフの軸を設定する

'グリッドの設定(タイトル、スクロール)
Chart1.ChartAreas(0).AxisX.ScrollBar.IsPositionedInside = False
Chart1.ChartAreas(0).AxisY.ScrollBar.IsPositionedInside = False
Chart1.ChartAreas(0).AxisX.LabelStyle.Enabled = False
Chart1.ChartAreas(0).AxisY.LabelStyle.Enabled = False
Chart1.ChartAreas(0).AxisY2.LabelStyle.Enabled = False
'グリッドの設定(X軸)
Chart1.ChartAreas(0).AxisX.MajorTickMark.Enabled = False
Chart1.ChartAreas(0).AxisX.MajorGrid.LineDashStyle = ChartDashStyle.Dash
Chart1.ChartAreas(0).AxisX.MinorGrid.Enabled = True
Chart1.ChartAreas(0).AxisX.MinorTickMark.Enabled = True
Chart1.ChartAreas(0).AxisX.MinorGrid.LineDashStyle = ChartDashStyle.Dash
'グリッドの設定(Y軸)
Chart1.ChartAreas(0).AxisY.MajorGrid.Enabled = True
Chart1.ChartAreas(0).AxisY.MajorTickMark.Enabled = False
Chart1.ChartAreas(0).AxisY.Maximum = 10000
Chart1.ChartAreas(0).AxisY.Minimum = 0

Chart1のグラフの軸を管理するChartAreasオブジェクトの設定を行います。(0)の番号は軸のメンバーの番号です。今回は軸は1つなので0を指定しています。

AxisXはX軸のオブジェクト、AxisYはY軸のオブジェクトです。各軸の設定の追加はAxisX、AxisYオブジェクトのメンバーを指定して行います。(以下ではAxisX、AxisYオブジェクトの表記は省略)

ScrollBarはデフォルトで使用になっていますが、スクロールバーの表示のデフォルトはグラフエリアの内側になっています。外側に配置する場合はScrollBarのIsPositionedInsideをTrueに変更します。

LabelStyle.Enabledプロパティは軸のラベルの表示を選択します。X軸、Y軸ともに軸のラベルを使用しないのでFalseにしています。AxisY2をFalseにすると第2軸(右側の軸)のラベル表示がなくなるためグラフの表示範囲が広がります。

MajorGridを使用する場合はEnableをTrueにします。MajorTickMarkは軸の外側にグリッドの線を表示する設定です。MajorGridのLineDashStyleはグリッド線の種別を指定する設定です。例ではDash(破線)を指定しています。

MinorGridについてもMajorGridと同様の設定ですが、MinorGridはMajorGridを分割するグリッド線です。デフォルトではFalseになっているため必要に応じて設定します。

Y軸についても同様にMajorGrid及びMinorGridの設定ができますが、Y軸のグリッド線をすべて使用すると線が多くなり見にくいためデフォルト設定のままにしています。

Y軸はスケーリングした値を使用してトレンドグラフを表示するためMaximumに10000、Minimumに0を指定して範囲固定しています。

広告

カーソル位置の設定と表示

Private Sub Chart1_PostPaint(sender As Object, e As ChartPaintEventArgs) Handles Chart1.PostPaint
    '省略
    Chart1.ChartAreas(0).CursorX.SetCursorPosition(cnt)
    ind = Chart1.Series(cursor_no).Points(cnt).YValues(0)
    Chart1.ChartAreas(0).CursorY.SetCursorPosition(ind)

    Label1.Text = Chart1.Series(0).Points(cnt).AxisLabel
    Label2.Text = HumidCng(Chart1.Series(0).Points(cnt).YValues(0)) & "%"
    Label3.Text = Chart1.Series(1).Points(cnt).AxisLabel
    Label4.Text = TempCng(Chart1.Series(1).Points(cnt).YValues(0)) & "℃"
End Sub

カーソルの位置や表示についてはVB.NET(VB2022)のChartでグラフを表示する方法で説明しています。ここではカーソル欄に表示するタイムスタンプ、温度、湿度の表示について説明します。

X軸のカーソル位置の指定はCursorXのSetCursorPosition()メソッドを使用します。引数にデータの番号を指定するとX軸のカーソルが表示できます。

Y軸のカーソル位置の指定はCursorYのSetCursorPosition()メソッドを使用します。X軸のカーソルで算出したcntを使用してSeries().Points(cnt).YValues(0)プロパティでY軸のデータを取得して引数に指定します。

カーソル欄に表示するタイムスタンプは7行目のAxisLabelプロパティで取得してLabel1のテキストプロパティに指定します。9行目もタイムスタンプを取得して表示しています。

湿度のデータは8行目のYValues(0)プロパティで取得したデータを換算して表示します。温度のデータは10行目のYValues(0)プロパティで取得したデータを換算して表示します。

'湿度データを換算
Private Function HumidChg(str As String) As String
    Dim rtn As String
    rtn = (str / 100).ToString("0.00")
    Return rtn
End Function
'温度データを換算
Private Function TempChg(str As String) As String
    Dim rtn As String
    rtn = ((1.2 * str - 4000) / 100).ToString("0.00")
    Return rtn
End Function

トレンドグラフのデータはスケーリング値を使用しているため実際の温度・湿度のデータに換算します。HumidChg()関数は湿度のデータを返す自作の関数です。湿度の範囲は0~100%なので100で割った値になります。小数点2桁で表示するためToString(“0.00”)を文字列のフォーマットを指定しています。

TempChg()関数は温度のデータを返す自作の関数です。-40℃~80℃の範囲は120になりますが、100倍値にしているため12000になります。これを10000にスケーリングしているため1.2倍した後-40℃(-4000)を引いて温度データ(100倍値)を生成しています。これを100で割ったとき小数点2桁で表示するためToString(“0.00”)で文字列のフォーマットを指定しています。

スポンサーリンク

動作確認(デバッグ)

デバッグは実際のアプリケーションの動作を模擬して実行するものです。デバッグの開始はエディタ上部の「▶開始」をクリックすると開始します。

動作確認(ESP32から取得した温湿度データのトレンドグラフ)
動作確認(ESP32から取得した温湿度データのトレンドグラフ)

ESP32とアクセスポイントに電源を入れてUDPサーバを待機させます。アプリの接続先にIPアドレス「192.168.11.10」、ポートを「10002」を入力して、「接続」をクリックするとUDP通信の初期化を行い送受信できるようになります。

ロガー操作欄の「開始」をクリックするとアプリから10秒毎に「CHARTAPP」を送信します。ESP32は温湿度データを含む電文で応答します。10秒毎に温湿度データのトレンドグラフが更新されていく様子が分かります。「停止」をクリックすると定期的な送信を停止します。

「データ取得(瞬時)」をクリックするとボタンを押したタイミングの温湿度データの電文を取得できます。

アプリは受信した電文から温湿度データを取得してChartにデータを表示します。アプリは360件のログを表示するようにしているため3600秒(1時間)のトレンドグラフになります。部屋の加湿器をONして動作確認しましたが、湿度が徐々に上がっている様子がトレンドグラフから分かりました。

マウスのカーソルの移動に合わせてカーソル位置が更新されることが確認でき、カーソル位置の波形のデータ値がグループボックスのカーソルに表示されることが確認できました。

グラフは左クリックで範囲を指定して部分拡大することができます。拡大するとスクロールバーが表示され指定した範囲を拡大して表示できます。

カーソル対象を切り替えるとグループボックスの色を変更して対象の項目が分かるようにしています。ソースコードのMouseClickイベントの処理を参照してください。

コードのデバッグを行う場合は、コードエディタでブレークポイントを置くことで一時的にプログラムを停止させることができます。

ブレークポイントでデバッグする方法
ブレークポイントでデバッグする方法

ブレークポイントはコードエディターの最左部分をクリックして●マークが表示されれば設置できています。例ではButton1がクリックされた時に発生したイベントの先頭部分にブレークポイントを置いています。この状態でButton1をクリックするとブレークポイントでプログラムが一時停止します。

F11を押すとステップ実行になるため1行ずつ内容を確認しながらデバッグを行うことができます。上部の「▶続行(C)」をクリックするとブレークポイント状態から通常の動作に復帰します。

PR:(即戦力のスキルを身に着ける:DMM WEBCAMP 学習コース(はじめてのプログラミングコース))

ソースコード全体

ソースコードは記事作成時点において動作確認できていますが、使用しているライブラリの更新により動作が保証できなくなる可能性があります。また、コメントに誤記が含まれていることもありますが、参考にお使いいただければと思います。

Imports System.Net
Imports System.Net.NetworkInformation
Imports System.Net.Sockets
Imports System.Text
Imports System.Windows.Forms.DataVisualization.Charting

Public Class Form1

    Structure NET_INTERFACE
        Dim name As String
        Dim localip As IPAddress
    End Structure

    Const RXRGSZ As Integer = 256
    Const GLF_WAVE_X As String = "X"
    Const GLF_WAVE_1 As String = "Humidity"
    Const GLF_WAVE_2 As String = "Temperature"
    Const GLF_ROW As Integer = 360

    Const TIME_UP As Integer = 0
    Const TIME_OFF As Integer = -1
    Const TIM_RCV_MAX As Integer = 2

    Structure TYP_COMRING
        Dim rp As Integer
        Dim wp As Integer
        Dim dat() As Byte
    End Structure

    Structure HIST_DATA
        Dim timstp() As Date
        Dim humi() As Integer
        Dim temp() As Integer

        Sub Init()
            ReDim timstp(GLF_ROW - 1)
            ReDim humi(GLF_ROW - 1)
            ReDim temp(GLF_ROW - 1)
        End Sub
    End Structure

    Dim UdpCloseFlg As Boolean = False
    Dim ClientUdp As UdpClient
    Dim LocalNet() As NET_INTERFACE
    Dim localIp As IPAddress
    Dim localPort As Integer
    Dim remotePort As Integer
    Dim logbuf(16) As Byte
    Dim log As HIST_DATA
    Dim lifcnt As Integer
    Dim RxRing As TYP_COMRING '受信したデータを管理
    Dim Mpointer As Point 'マウス位置
    Dim cursor_no As Integer
    Dim CURSOR_COLOR As Color = Color.Gold
    Dim SERIES_COLOR_1 As Color = Color.Red
    Dim SERIES_COLOR_2 As Color = Color.Blue
    Dim GR_COLOR As Color = Color.White
    Dim GR_COLOR_2 As Color = Color.Black
    Dim timRcv As Integer = TIME_OFF

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim remoteIp As String = ""

        If Button1.BackColor = Color.LightGreen Then
            remoteIp = TextBox1.Text & "." &
                       TextBox2.Text & "." &
                       TextBox3.Text & "." &
                       TextBox4.Text

            localPort = TextBox8.Text
            remotePort = TextBox5.Text

            Try
                ClientUdp = New UdpClient(localPort)
                ClientUdp.Connect(remoteIp, remotePort) 'UDP接続
                ClientUdp.BeginReceive(AddressOf ReceiveCallback, ClientUdp) '受信待ちの発行

                Timer1.Enabled = True
                Button1.BackColor = Color.LightBlue
                Button2.BackColor = Color.LightGreen
            Catch ex As Exception

            End Try

        End If
    End Sub

    'UDPの受信と受信API発行
    Private Sub ReceiveCallback(AR As IAsyncResult)
        Dim i As Integer

        Try
            ' ソケット受信
            Dim loc_ip As New IPEndPoint(localIp, localPort)
            Dim getByte() As Byte = ClientUdp.EndReceive(AR, loc_ip)

            For i = 0 To getByte.Length - 1
                RxRingSet(getByte(i))
            Next
            ' ソケット非同期受信(System.AsyncCallback)
            ClientUdp.BeginReceive(AddressOf ReceiveCallback, ClientUdp)
        Catch ex As Exception

        End Try
    End Sub

    'LANポート検索
    Private Sub GetNetworkList()
        Dim netinterface = NetworkInterface.GetAllNetworkInterfaces
        Dim wk(netinterface.Length - 1) As NET_INTERFACE
        Dim i As Integer
        Dim j As Integer = 0
        Dim cnt As Integer = 0

        For Each netin In netinterface
            If netin.OperationalStatus = OperationalStatus.Up AndAlso
               netin.NetworkInterfaceType = NetworkInterfaceType.Ethernet Then

                wk(i).name = netin.Name
                cnt += 1

                Dim ipprperties As IPInterfaceProperties = netin.GetIPProperties

                If ipprperties IsNot Nothing Then
                    For Each ipadrs In ipprperties.UnicastAddresses
                        wk(i).localip = ipadrs.Address
                    Next
                End If

            End If

            i += 1
        Next

        ReDim LocalNet(cnt - 1)

        For i = 0 To wk.Length - 1
            If wk(i).name <> "" Then
                LocalNet(j) = wk(i)
                j += 1
            End If
        Next

    End Sub

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim i As Integer

        LabelInit()
        ComboBoxInit()
        RxRingInit()

        log.Init()

        For i = 0 To GLF_ROW - 1
            log.timstp(i) = Nothing
            log.humi(i) = 0
            log.temp(i) = 0
        Next
    End Sub

    Private Sub ComboBox1_MouseClick(sender As Object, e As MouseEventArgs) Handles ComboBox1.MouseClick
        ComboBoxInit()
    End Sub

    Private Sub ComboBox1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ComboBox1.SelectedIndexChanged
        localIp = LocalNet(ComboBox1.SelectedIndex).localip
    End Sub

    'ComboBoxの初期化
    Private Sub ComboBoxInit()
        Dim i As Integer
        Dim str As String

        GetNetworkList()
        ComboBox1.Items.Clear()

        For i = 0 To LocalNet.Length - 1
            str = LocalNet(i).name & " " & "IP:" & LocalNet(i).localip.ToString
            ComboBox1.Items.Add(str)
        Next

    End Sub

    'Labelの初期化
    Private Sub LabelInit()
        Label1.Text = "----"
        Label2.Text = "----"
        Label3.Text = "----"
        Label4.Text = "----"
    End Sub

    Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
        SendData()
    End Sub

    'UDPのパケットを送信
    Private Sub SendData()
        Dim data() As Byte = Encoding.GetEncoding("utf-8").GetBytes("CHARTAPP")

        If ClientUdp IsNot Nothing Then
            Try
                ClientUdp.Send(data, data.Length)
            Catch ex As Exception

            End Try
        End If
    End Sub

    '電文のチェック
    Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
        Dim sz As Integer
        Dim i As Integer
        Dim str As String = ""
        Dim rp As Integer

        If timRcv > TIME_UP Then
            timRcv -= 1
        ElseIf timRcv = TIME_UP Then
            timRcv = TIME_OFF
            RingRpAdd()
        End If

        sz = RxRing.wp - RxRing.rp

        If sz < 0 Then
            sz += RxRing.dat.Length
        End If

        If sz = 0 Then
            timRcv = TIME_OFF
        Else
            If timRcv = TIME_OFF Then
                timRcv = TIM_RCV_MAX
            End If

            If sz >= 8 Then
                rp = RxRing.rp
                For i = 0 To 8 - 1
                    str &= Chr(RxRing.dat(rp))

                    rp += 1
                    If rp >= RxRing.dat.Length Then
                        rp = 0
                    End If
                Next

                If str = "CHARTAPP" Then
                    If sz >= 16 Then
                        timRcv = TIME_OFF
                        For i = 0 To 16 - 1
                            logbuf(i) = RxRing.dat(RxRing.rp)
                            RingRpAdd()
                        Next
                        ChartDataSet()
                    End If
                Else
                    RingRpAdd()
                End If

            End If
        End If
    End Sub

    Private Sub Timer2_Tick(sender As Object, e As EventArgs) Handles Timer2.Tick
        SendData()
    End Sub

    'Chartのデータをセットする
    Private Sub ChartDataSet()
        Dim dt As New DataTable
        Dim row As DataRow
        Dim humi As Integer
        Dim humi_log As Integer
        Dim temp As Integer
        Dim temp_log As Integer
        Dim humibuf(GLF_ROW) As Integer
        Dim tempbuf(GLF_ROW) As Integer
        Dim datebuf(GLF_ROW) As Date
        'ログのデータを生成
        humi = (CInt(logbuf(8)) << 8) + logbuf(9)
        humi_log = (CInt(logbuf(10)) << 8) + logbuf(11)
        temp = (CInt(logbuf(12)) << 8) + logbuf(13)
        temp_log = (CInt(logbuf(14)) << 8) + logbuf(15)
        'データをずらして格納
        log.humi.CopyTo(humibuf, 1)
        log.temp.CopyTo(tempbuf, 1)
        log.timstp.CopyTo(datebuf, 1)

        For i = 1 To GLF_ROW - 1
            log.timstp(i) = datebuf(i)
            log.humi(i) = humibuf(i)
            log.temp(i) = tempbuf(i)
        Next
        '最新のデータを格納
        log.timstp(0) = Now
        log.humi(0) = humi_log
        log.temp(0) = temp_log
        'グラフデータセット
        dt.Columns.Add(GLF_WAVE_X)
        dt.Columns.Add(GLF_WAVE_1)
        dt.Columns.Add(GLF_WAVE_2)

        For i = 0 To GLF_ROW - 1
            row = dt.NewRow() '行項目を新規追加
            row(GLF_WAVE_X) = log.timstp(GLF_ROW - 1 - i)
            row(GLF_WAVE_1) = log.humi(GLF_ROW - 1 - i)
            row(GLF_WAVE_2) = log.temp(GLF_ROW - 1 - i)
            dt.Rows.Add(row)
        Next

        Chart1.Series.Clear() 'グラフの項目をクリア
        Chart1.DataSource = dt 'DataTableと関連付け
        Chart1.DataBind() 'コントロールををデータソースにバインド

        Chart1SetSeries(GLF_WAVE_1, SERIES_COLOR_2)
        Chart1SetSeries(GLF_WAVE_2, SERIES_COLOR_1)
        Chart1SetAreas()
    End Sub

    'RxRingの初期化を行う
    Private Sub RxRingInit()
        RxRing.wp = 0
        RxRing.rp = 0
        ReDim RxRing.dat(RXRGSZ - 1)
    End Sub

    'RxRingのwpを更新する
    Public Sub RxRingSet(dat As Byte)

        RxRing.dat(RxRing.wp) = dat

        RxRing.wp += 1
        If RxRing.wp = RxRing.dat.Length Then
            RxRing.wp = 0
        End If
    End Sub

    'RxRingのrpを更新する
    Private Sub RingRpAdd()

        RxRing.rp += 1
        If RxRing.rp >= RxRing.dat.Length Then
            RxRing.rp = 0
        End If
    End Sub

    Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
        If Button2.BackColor = Color.LightGreen Then
            '//クライアントのインスタンスが有ったら
            If ClientUdp IsNot Nothing Then
                ClientUdp.Close()
                Button2.BackColor = Color.LightBlue
                Button1.BackColor = Color.LightGreen
            End If
        End If
    End Sub

    'Chartに項目を追加
    Private Sub Chart1SetSeries(str As String, color As Color)

        Chart1.Series.Add(str)
        Chart1.Series(str).ChartType = SeriesChartType.Spline
        Chart1.Series(str).Color = color
        Chart1.Series(str).XValueMember = GLF_WAVE_X
        Chart1.Series(str).YValueMembers = str
    End Sub

    'Chartの軸をセット
    Private Sub Chart1SetAreas()
        'グリッドの設定(タイトル、スクロール)
        'Chart1.ChartAreas(0).AxisX.IsMarginVisible = True
        'Chart1.ChartAreas(0).AxisY.IsMarginVisible = True
        Chart1.ChartAreas(0).AxisX.ScrollBar.IsPositionedInside = False
        Chart1.ChartAreas(0).AxisY.ScrollBar.IsPositionedInside = False
        'Chart1.ChartAreas(0).AxisX.Title = "タイムスタンプ"
        'Chart1.ChartAreas(0).AxisY.Title = "スケール"
        Chart1.ChartAreas(0).AxisX.LabelStyle.Enabled = False
        Chart1.ChartAreas(0).AxisY.LabelStyle.Enabled = False
        Chart1.ChartAreas(0).AxisY2.LabelStyle.Enabled = False

        'グリッドの設定(X軸)
        'Chart1.ChartAreas(0).AxisX.MajorGrid.Enabled = True
        Chart1.ChartAreas(0).AxisX.MajorTickMark.Enabled = False
        Chart1.ChartAreas(0).AxisX.MajorGrid.LineDashStyle = ChartDashStyle.Dash
        Chart1.ChartAreas(0).AxisX.MinorGrid.Enabled = True
        Chart1.ChartAreas(0).AxisX.MinorTickMark.Enabled = True
        Chart1.ChartAreas(0).AxisX.MinorGrid.LineDashStyle = ChartDashStyle.Dash
        'グリッドの設定(Y軸)
        Chart1.ChartAreas(0).AxisY.MajorGrid.Enabled = True
        Chart1.ChartAreas(0).AxisY.MajorTickMark.Enabled = False
        'Chart1.ChartAreas(0).AxisY.MajorGrid.LineDashStyle = ChartDashStyle.Dash
        'Chart1.ChartAreas(0).AxisY.MinorGrid.Enabled = True
        'Chart1.ChartAreas(0).AxisY.MinorTickMark.Enabled = True
        'Chart1.ChartAreas(0).AxisY.MinorGrid.LineDashStyle = ChartDashStyle.Dash
        Chart1.ChartAreas(0).AxisY.Maximum = 10000
        Chart1.ChartAreas(0).AxisY.Minimum = 0

        'カーソルの設定
        Chart1.ChartAreas(0).CursorX.LineColor = CURSOR_COLOR
        Chart1.ChartAreas(0).CursorY.LineColor = CURSOR_COLOR

        Chart1.ChartAreas(0).CursorX.IsUserEnabled = True
        Chart1.ChartAreas(0).CursorY.IsUserEnabled = True
        Chart1.ChartAreas(0).CursorX.IsUserSelectionEnabled = True
        Chart1.ChartAreas(0).CursorY.IsUserSelectionEnabled = True

        Select Case cursor_no
            Case 0
                GroupColor(CURSOR_COLOR, GR_COLOR)
            Case 1
                GroupColor(GR_COLOR, CURSOR_COLOR)
        End Select

        Label1.Text = "----"
        Label2.Text = "----"
        Label3.Text = "----"
        Label4.Text = "----"
    End Sub

    Private Sub Chart1_PostPaint(sender As Object, e As ChartPaintEventArgs) Handles Chart1.PostPaint
        Dim axis_x = Chart1.ChartAreas(0).AxisX
        Dim axis_y = Chart1.ChartAreas(0).AxisY
        Dim x1 = axis_x.ValueToPixelPosition(axis_x.Minimum)  'グラフ領域左端のX座標
        Dim x2 = axis_x.ValueToPixelPosition(axis_x.Maximum)  'グラフ領域右端のX座標
        Dim y1 = axis_y.ValueToPixelPosition(axis_y.Maximum)  'グラフ領域上部のY座標
        Dim y2 = axis_y.ValueToPixelPosition(axis_y.Minimum)  'グラフ領域下部のY座標
        Dim xv1 = axis_x.ValueToPixelPosition(axis_x.ScaleView.ViewMinimum)  'グラフ領域左端のX座標
        Dim xv2 = axis_x.ValueToPixelPosition(axis_x.ScaleView.ViewMaximum)  'グラフ領域右端のX座標
        Dim yv1 = axis_y.ValueToPixelPosition(axis_y.ScaleView.ViewMaximum)  'グラフ領域上部のY座標
        Dim yv2 = axis_y.ValueToPixelPosition(axis_y.ScaleView.ViewMinimum)  'グラフ領域下部のY座標
        Dim w As Double
        Dim cnt As Integer
        Dim ind As Integer

        If Double.IsNaN(x1) = False And Double.IsNaN(x2) = False And
            Double.IsNaN(y1) = False And Double.IsNaN(y2) = False Then
            'グラフの軸が描写されているか
            If xv1 <= Mpointer.X And Mpointer.X <= xv2 And
               yv1 <= Mpointer.Y And Mpointer.Y <= yv2 Then
                'マウスカーソルがグラフの軸以内であるか
                w = (x2 - x1) / GLF_ROW
                cnt = (CDbl(Mpointer.X) - x1) / w

                If cnt < 0 Then
                    cnt = 0
                ElseIf cnt >= GLF_ROW Then
                    cnt = GLF_ROW - 1
                End If

                Chart1.ChartAreas(0).CursorX.SetCursorPosition(cnt)
                ind = Chart1.Series(cursor_no).Points(cnt).YValues(0)
                Chart1.ChartAreas(0).CursorY.SetCursorPosition(ind)

                Label1.Text = Chart1.Series(0).Points(cnt).AxisLabel
                Label2.Text = HumidChg(Chart1.Series(0).Points(cnt).YValues(0)) & "%"
                Label3.Text = Chart1.Series(1).Points(cnt).AxisLabel
                Label4.Text = TempChg(Chart1.Series(1).Points(cnt).YValues(0)) & "℃"
            End If
        End If

    End Sub
    '湿度のデータを換算
    Private Function HumidChg(str As String) As String
        Dim rtn As String

        rtn = (str / 100).ToString("0.00")
        Return rtn
    End Function
    '温度データを換算
    Private Function TempChg(str As String) As String
        Dim rtn As String

        rtn = ((1.2 * str - 4000) / 100).ToString("0.00")
        Return rtn
    End Function

    Private Sub Chart1_MouseMove(sender As Object, e As MouseEventArgs) Handles Chart1.MouseMove

        Mpointer = e.Location 'マウス位置獲得
        Chart1.Invalidate() 'グラフ更新
    End Sub

    Private Sub Chart1_MouseClick(sender As Object, e As MouseEventArgs) Handles Chart1.MouseClick
        Dim no = e.Button

        If no = MouseButtons.Right Then
            If Chart1.Series IsNot Nothing Then
                If cursor_no >= Chart1.Series.Count - 1 Then
                    cursor_no = 0
                Else
                    cursor_no += 1
                End If
            End If

            Select Case cursor_no
                Case 0
                    GroupColor(CURSOR_COLOR, GR_COLOR)
                Case 1
                    GroupColor(GR_COLOR, CURSOR_COLOR)
            End Select
        End If

    End Sub

    Private Sub Button4_Click(sender As Object, e As EventArgs) Handles Button4.Click

        If Button4.BackColor = Color.LightGreen Then
            Button4.BackColor = Color.LightBlue
            Button5.BackColor = Color.LightGreen
            Timer2.Interval = 10000
            Timer2.Enabled = True
        End If
    End Sub

    Private Sub Button5_Click(sender As Object, e As EventArgs) Handles Button5.Click

        If Button5.BackColor = Color.LightGreen Then
            Button5.BackColor = Color.LightBlue
            Button4.BackColor = Color.LightGreen
            Timer2.Enabled = False
        End If
    End Sub

    'カーソル欄の色変更
    Private Sub GroupColor(c1 As Color, c2 As Color)

        GroupBox7.BackColor = c1
        GroupBox7.ForeColor = Chart1.Series(0).Color

        GroupBox8.BackColor = c2
        If Chart1.Series.Count >= 2 Then
            GroupBox8.ForeColor = Chart1.Series(1).Color
        Else
            GroupBox8.ForeColor = GR_COLOR_2
        End If
    End Sub

End Class

コントロールの番号やイベントハンドラーなどのプロパティ名などを変更している場合はソースコードのイベントハンドラーをお使いのイベントハンドラーに置き換えてください。

Timer2のインターバルを指定するようにListBoxを追加したりログの表示件数を拡張したりすることでトレンドグラフとして保存する範囲を拡張することができます。

関連リンク

VB.NET(VB2022)のデスクトップアプリで動作確認したことを下記リンクにまとめています。

VB.NET(VB2022)のデスクトップアプリ開発でできること

マイクロソフトはVisual Studio以外にもVSCodeという便利なエディターを提供しています。下記リンクではVSCodeのインストールの仕方からC言語開発環境の作り方までをまとめています。

VSCodeをインストールしてC/C++の開発環境を作る

PR:無料トライアル実施中【PC専用】AIスライド資料作成ツールの利用:イルシル

最後まで、読んでいただきありがとうございました。

タイトルとURLをコピーしました