グラフ描画

タグの編集
投稿者 vb素人  (学生) 投稿日時 2017/7/27 14:32:51
Chartコントロールを使ってグラフ描画に挑戦しています。

データを表示させる前に、グラフを表示させるところでつまづきました。

ChartTypeをPointに設定して、↓のコードでグラフを表示させようとすると、
Chartが真っ白になってしまいます。


描きたいグラフは、
X軸: 0~1023 (10bit)
Y軸: 0~1023 (10bit)
でY軸、X軸ともに511の所で交わるようにしたいです。
(↓のコードもそのつもりで設定しています。)


○Chart1の"Seriesコレクションエディター"

ChartArea: ChartArea1
ChartType: Point






○試しているコード
        Dim minX As Double = 0
        Dim maxX As Double = 1023
        Dim minY As Double = 0
        Dim maxY As Double = 1023

        Chart1.ChartAreas(0).AxisX.Crossing = 511
        Chart1.ChartAreas(0).AxisX.Maximum = maxX
        Chart1.ChartAreas(0).AxisX.Minimum = minX
        Chart1.ChartAreas(0).AxisY.Crossing = 511
        Chart1.ChartAreas(0).AxisY.Maximum = maxY
        Chart1.ChartAreas(0).AxisY.Minimum = minY

        ' Set Arrow Style
        Chart1.ChartAreas("ChartArea1").AxisY.ArrowStyle = AxisArrowStyle.SharpTriangle
        Chart1.ChartAreas("ChartArea1").AxisX.ArrowStyle = AxisArrowStyle.SharpTriangle
        Chart1.ChartAreas("ChartArea1").AxisY.Title = "Y軸"
        Chart1.ChartAreas("ChartArea1").AxisX.Title = "X軸"
        ' Enable all elements
        Chart1.ChartAreas("ChartArea1").AxisX.MinorGrid.Enabled = True
        Chart1.ChartAreas("ChartArea1").AxisX.MinorTickMark.Enabled = True
        Chart1.ChartAreas("ChartArea1").AxisY.MinorGrid.Enabled = True
        Chart1.ChartAreas("ChartArea1").AxisY.MinorTickMark.Enabled = True

        ' Set Grid lines and tick marks interval
        Chart1.ChartAreas("ChartArea1").AxisX.MajorGrid.Interval = 1
        Chart1.ChartAreas("ChartArea1").AxisX.MajorTickMark.Interval = 1
        Chart1.ChartAreas("ChartArea1").AxisX.MinorGrid.Interval = 0.1
        Chart1.ChartAreas("ChartArea1").AxisX.MinorTickMark.Interval = 0.1
        Chart1.ChartAreas("ChartArea1").AxisY.MajorGrid.Interval = 1
        Chart1.ChartAreas("ChartArea1").AxisY.MajorTickMark.Interval = 1
        Chart1.ChartAreas("ChartArea1").AxisY.MinorGrid.Interval = 0.1
        Chart1.ChartAreas("ChartArea1").AxisY.MinorTickMark.Interval = 0.1

        Chart1.ChartAreas("ChartArea1").AxisX.Interval = 1
        Chart1.ChartAreas("ChartArea1").AxisY.Interval = 1

        ' Set Line Color
        Chart1.ChartAreas("ChartArea1").AxisX.MinorGrid.LineColor = Color.SkyBlue
        Chart1.ChartAreas("ChartArea1").AxisX.MajorGrid.LineColor = Color.SkyBlue

        Chart1.ChartAreas("ChartArea1").AxisY.MinorGrid.LineColor = Color.SkyBlue
        Chart1.ChartAreas("ChartArea1").AxisY.MajorGrid.LineColor = Color.SkyBlue

        ' Set Line Style
        Chart1.ChartAreas("ChartArea1").AxisX.MajorTickMark.LineDashStyle = ChartDashStyle.Solid
        Chart1.ChartAreas("ChartArea1").AxisX.MinorGrid.LineDashStyle = ChartDashStyle.Dot

        Chart1.ChartAreas("ChartArea1").AxisY.MajorTickMark.LineDashStyle = ChartDashStyle.Solid
        Chart1.ChartAreas("ChartArea1").AxisY.MinorGrid.LineDashStyle = ChartDashStyle.Dot

        ' Set Line Width
        Chart1.ChartAreas("ChartArea1").AxisX.MajorGrid.LineWidth = 1
        Chart1.ChartAreas("ChartArea1").AxisY.MajorGrid.LineWidth = 1
投稿者 shu  (社会人) 投稿日時 2017/8/1 10:02:29
ChartAreaの設定だけでなく
データを設定しないとグラフは表示されませんよ?
投稿者 (削除されました)  () 投稿日時 2017/8/8 15:25:39
(削除されました)
投稿者 vb素人  (学生) 投稿日時 2017/8/8 15:42:07
shu様

初歩的なミスを指摘していただき、ありがとうございます。

◎DataPointコレクションエディター
ここでデータを追加しました。(とりあえず、x=300, y=600)
これでグラフにデータを表示させることができました。

次の段階として、
Textboxに入力された値をグラフにプロットさせるにはどのようにすれば良いのでしょうか。

TextBox1⇒Xの値
TextBox2⇒Yの値 
Button1 Clickで、グラフにプロットさせたいです。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2017/8/8 17:48:49
Chart1.Series(シリーズ名).Points.Add あるいは .AddXY を使います。

たとえば下記では、データとして固定値をセットしていますが、これを
CDbl(TextBox1.Text) あるいは CDbl(NumericUpDown1.Value) などから
受け取るようにすれば良いかと。


'Imports System.Windows.Forms.DataVisualization.Charting 
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button2.Click

    Chart1.Series.Clear()   '既存の Series をクリアする場合 
    Dim s As Series

    '方法1 
    
    ''既存の Series を取得する場合(デザイン時に設定しておくなど) 
    ''s = Chart1.Series("Series2") 
    
    '新規に作成する場合 
    s = Chart1.Series.Add("Series2")
    s.ChartType = SeriesChartType.Line
    
    s.Points.Add(New DataPoint(400.0, 750.0))
    s.Points.Add(New DataPoint(420.0, 800.0))
    s.Points.Add(New DataPoint(440.0, 850.0))
    s.Points.AddXY(340.0, 900.0)
    s.Points.AddXY(330.0, 950.0)
    s.Points.AddXY(320.0, 1000.0)

    '方法2:データを割り当ててから 
    s = New Series()
    s.ChartType = SeriesChartType.Line
    s.Name = "Series1"
    s.Points.Add(New DataPoint(500.0, 200.0))
    s.Points.Add(New DataPoint(510.0, 300.0))
    s.Points.Add(New DataPoint(520.0, 400.0))
    s.Points.AddXY(550.0, 500.0)
    s.Points.AddXY(560.0, 600.0)
    s.Points.AddXY(570.0, 700.0)
    Chart1.Series.Add(s)
End Sub
投稿者 vb素人  (学生) 投稿日時 2017/8/20 08:43:10
魔界の仮面弁士さま

返事が遅れてすみません。
教えていただいた方法でグラフにデータをプロットすることができました。

次の段階として、
少し前に掲示板で質問させていただいたシリアルデータを使いたいと思っています。
受信したシリアルデータ(XとY)は、受信するたびに、Textbox1(Xのデータ)とTextbox2(Yのデータ)に表示されます。

このTextBoxに表示された数値をグラフ上にプロットしたいと考えています。


今テストしているプログラムです。


    Private Delegate Sub Delegate_RcvXDataToTextBox(xdec As String)
    Private Delegate Sub Delegate_RcvYDataToTextBox(ydec As String)


    Private Sub SerialPort1_DataReceived(sender As Object, e As SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived

  '細かい所は省略します。
    '受信データを10進数表記に変換してxdecと、ydecをInvokeします。
     Dim datax As Byte() = {bin(2), bin(1)}
     Dim xbyte As UShort = BitConverter.ToUInt16(datax, 0)    '符号なし2 バイト整数に変換 
     Dim xdec As String = Convert.ToString(xbyte, 10)           '10進数表記の "1023" に変換 
     Invoke(New Delegate_RcvXDataToTextBox(AddressOf RcvXDataToTextBox), xdec)

     Dim datay As Byte() = {bin(4), bin(3)}
     Dim ybyte As UShort = BitConverter.ToUInt16(datay, 0)    '符号なし2 バイト整数に変換 
     Dim ydec As String = Convert.ToString(ybyte, 10)           '10進数表記の "1023" に変換 
     Invoke(New Delegate_RcvYDataToTextBox(AddressOf RcvYDataToTextBox), ydec)

    End Sub

③xdecとydecの値をTextBoxへ表示させます。

    Private Sub RcvXDataToTextBox(xdec As String)

        '受信データをテキストボックスの最後に追記する.
        If IsNothing(xdec) = False Then
            RcvXTextBox.AppendText(xdec)

        End If

    End Sub

    Private Sub RcvYDataToTextBox(ydec As String)

        '受信データをテキストボックスの最後に追記する.
        If IsNothing(ydec) = False Then
            RcvYTextBox.AppendText(ydec)

        End If

    End Sub


ここから質問なのですが、次のコードでグラフにTextboxに入力したデータをプロットすることができました。
④TextBox1,2 (①~③のシリアル受信データとは別のテキストボックス)に数値を入力して、グラフへ表示させるプログラム
 (グラフ表示のテスト用にTextBox1,2を用意しました。)
        Chart1.Series.Clear()
        Dim s As Series

        Dim X As Double = 0
        Dim Y As Double = 0

        X = CDbl(TextBox1.Text)
        Y = CDbl(TextBox2.Text)

        s = Chart1.Series.Add("E")
        s.ChartType = SeriesChartType.Point

        s.Points.AddXY(X, Y)


シリアルデータを受信するたびにグラフにデータをプロットしたいのですが、
どのようにしたら良いのかが分かりません。

④の内容を①~③のどこに記載すべきかが分かっておりません。
教えてもらえると助かります。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2017/8/20 16:17:47
デリゲートを通じてTextBox にデータを渡せるようになったのであれば、同じ理屈でデリゲートでChart に渡すこともできると思うですが、どういった点で問題があったのでしょうか?

デリゲートに慣れないのであれば、受信したデータを排他制御したスタックに積んでおき、それをTimer なり Paint なりといった、受信タイミングではなくUI 側のタイミングでプロットする形でも良いでしょう。
投稿者 (削除されました)  () 投稿日時 2017/8/22 09:24:24
(削除されました)
投稿者 vb素人  (学生) 投稿日時 2017/8/22 09:34:04
魔界の仮面弁士さま

返信ありがとうございます。
デリゲートを使う方法で試しているのですが(下記④、⑤の部分)、うまくいきません。

実行すると
★の位置で「パラメーター カウントが一致しません。」というエラーが出てしまいます。




'①
    Private Delegate Sub Delegate_RcvXDataToTextBox(xdec As String)
    Private Delegate Sub Delegate_RcvYDataToTextBox(ydec As String)

'②
    Private Sub SerialPort1_DataReceived(sender As Object, e As SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived

  '細かい所は省略します。
    '受信データを10進数表記に変換してxdecと、ydecをInvokeします。
     Dim datax As Byte() = {bin(2), bin(1)}
     Dim xbyte As UShort = BitConverter.ToUInt16(datax, 0)    '符号なし2 バイト整数に変換 
     Dim xdec As String = Convert.ToString(xbyte, 10)           '10進数表記の "1023" に変換 
     Invoke(New Delegate_RcvXDataToTextBox(AddressOf RcvXDataToTextBox), xdec)

     Dim datay As Byte() = {bin(4), bin(3)}
     Dim ybyte As UShort = BitConverter.ToUInt16(datay, 0)    '符号なし2 バイト整数に変換 
     Dim ydec As String = Convert.ToString(ybyte, 10)           '10進数表記の "1023" に変換 
     Invoke(New Delegate_RcvYDataToTextBox(AddressOf RcvYDataToTextBox), ydec)

    End Sub

'③xdecとydecの値をTextBoxへ表示させます。
    Private Sub RcvXDataToTextBox(xdec As String)

        Dim X As Integer
        '受信データをテキストボックスの最後に追記する.
        If IsNothing(xdec) = False Then
            RcvXTextBox.AppendText(xdec)
            X = CInt(xdec)
'★
           Invoke(New Delegate_RcvDataToGraph(AddressOf RcvDataToGraph), X)
        End If

    End Sub

    Private Sub RcvYDataToTextBox(ydec As String)

        Dim Y As Integer
        '受信データをテキストボックスの最後に追記する.
        If IsNothing(ydec) = False Then
            RcvYTextBox.AppendText(ydec)
            Y = CInt(ydec)
            Invoke(New Delegate_RcvDataToGraph(AddressOf RcvDataToGraph), Y)
        End If

    End Sub

'④グラフへデータを渡すデリゲート
    Private Delegate Sub Delegate_RcvYDataToGraph(X As Integer, Y As Integer)


'⑤グラフ表示
    Private Sub RcvYDataToGraph(X As Integer, Y As Integer)
        Chart1.Series.Clear()   '既存の Series をクリアする場合 
        Dim s As Series

        s = Chart1.Series.Add("E")
        s.ChartType = SeriesChartType.Point

        s.Points.AddXY(X, Y)

    End Sub
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2017/8/22 12:01:11
> Dim xbyte As UShort = BitConverter.ToUInt16(datax, 0)
変数名を xbyte にしていますが、"byte" という語は、
8bit なデータ型を連想させるので、適切では無いと思います。


> Private Sub RcvXDataToTextBox(xdec As String)
>  Invoke(New Delegate_RcvDataToGraph(AddressOf RcvDataToGraph), X)
> End Sub

RcvXDataToTextBox は UI スレッドで呼ばれているのに、
何故、そこからさらに「Invoke」しなおしているのでしょうか?


> Invoke(New Delegate_RcvXDataToTextBox(AddressOf RcvXDataToTextBox), xdec)
> Invoke(New Delegate_RcvYDataToTextBox(AddressOf RcvYDataToTextBox), ydec)

2 つのデータをそれぞれ別々に Invoke するのは、実行コストが高くなります。



1 回の受信で、 x 値 と y 値 の 2 つを同時に受け取れるのであれば、
その 2 つをまとめて渡すためのデリゲートを用意しておき、それを
SerialPort1_DataReceived イベントのスレッドから Invoke するようにします。


処理イメージとしてはこんな感じ。

Private Sub SerialPort1_DataReceived(sender As Object, e As SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
 Dim bin(0 To 5) As Byte
 ' :
 ' :
 Dim x = BitConverter.ToUInt16({bin(2), bin(1)}, 0)
 Dim y = BitConverter.ToUInt16({bin(4), bin(3)}, 0)
 Received(x, y)
End Sub


'受信データを画面に表示するためのメソッド
Private Sub Received(x As UShort, y As UShort)
 If InvokeRequired Then

  '別スレッドからの呼び出しなので、Invoke しなおします
  Invoke(New Action(Of UShort, UShort)(AddressOf Received), x, y)

 Else
  '同じスレッドからの呼び出しならそのまま実行

  'AddXY メソッドの引数は本来 Double 型ですが、
  'UShort → Double への拡大変換は自動的に行われるので
  'CDbl 等での変換は省略して構いません。
  Chart1.Series(要素).Points.AddXY(x, y)

  'AppendText メソッドの引数は String 型なので
  '数値から文字列への変換は明示的に行う必要があります。
  RcvXTextBox.AppendText(Str(x))
  RcvYTextBox.AppendText(Str(y))

  'TextBox に履歴的に追記するのではなく、
  '常に最新値を表示したいのであれば、下記のようにします。
  'RcvXTextBox.Text = CStr(x)
  'RcvYTextBox.Text = CStr(x)
 End If
End Sub



※注1※
今までのコードを見ていると、何のために Invoke しているのかを
ただしく理解していないように思えたので、多少冗長的ではありますが、
あえて InvokeRequired プロパティで判定するような処理にしてみました。

SerialPort1_DataReceived のスレッドが UI スレッドでない事は明確なので、
InvokeRequired で判定したりせず、元のように、DataReceived イベントの時点で
Invoke するようにしても構いません。


※注2※
今回のコードでは、TextBox に渡すために文字列化する際に、
Convert.ToString や CStr ではなく、Str を使っています。

これはたとえば、「12」「14」「13」が渡されて来たときに、
TextBox に " 12 14 13" と空白付きで表示させて、
データの区切りを分かりやすくするためです。

もしも Convert.ToString や CStr を使っていると、"121413" になりますが、
これだと、「12」「14」「13」なのか「1」「2」「141」「3」なのか
判断できないと思ったので手を加えていますが、もしも
空白が無い方が都合が良いのであれば、適宜見直してください。
投稿者 vb素人  (学生) 投稿日時 2017/8/22 14:55:44
魔界の仮面弁士さま

返信ありがとうございます。
ご指摘の通り、Invokeの使い方を正しく理解しておりませんでした。

教えていただいた処理イメージを参考にしながら、
何とかシリアルデータをグラフに表示させることができました。
ありがとうございます。

また、Strの使用についてもシリアルデータの時に教えていただいていたのにもかかわらず、
今回使っておりませんでした。
おっしゃる通りで、区切りの空白が無いと表示がずれることがあります。


最後に、グラフについて1つ教えてください。

グラフ上に受信データをプロットしているのですが、
プロットしているプロット点(マーカー)に画像などを指定することは可能でしょうか。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2017/8/22 16:23:20
MarkerImage プロパティですね。
画像ファイル名や URL 、あるいは画像リソースをあらわすキー名を指定します。
Chart1.BackImage なども同様。

http://hanatyan.sakura.ne.jp/patio/read.cgi?mode=view2&f=262&no=11



キー名で指定する場合は、
 Dim img As Image = My.Resources.画像リソース
 Chart1.Images.Add( New NamedImage("キー", img) )
のように登録しておいて、
 Chart1.Series(要素).MarkerImage = "キー"
などとします。
投稿者 vb素人  (学生) 投稿日時 2017/8/22 16:54:27
魔界の仮面弁士さま

ありがとうございました。
無事シリアルデータを受信し、グラフにプロットさせることができました。

次は、データの保存にチャレンジしてみます。