散布図のグラフ データ同士を線で接続する への返答

投稿で使用できる特殊コードの説明。(別タブで開きます。)
本名は入力しないようにしましょう。
投稿した後で削除するときに使うパスワードです。返答があった後は削除できません。
返答する人が目安にします。相手が小学生か社会人かで返答の仕方も変わります。
最初の投稿が質問の場合、質問者が解決時にチェックしてください。(以降も追加書き込み・返信は可能です。)
※「過去ログ」について書くときはその過去ログのURLも書いてください。

以下の返答は逆順(新しい順)に並んでいます。

投稿者 vb素人  (学生) 投稿日時 2017/9/19 12:16:38
魔界の仮面弁士さま

ありがとうございます。
無事解決することができました。

蛇足の説明がとても分かりやすく理解することができました。

データ系統(Series)と凡例の関連付けが理解できていませんでした。
一度Seriesを用意したら(Load時など)、その後、再びSeriesを用意しなくても、
Chart1.Series(0)でそれを利用すれば良いということを勉強させてもらいました。

ありがとうございました。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2017/9/16 11:47:50
シリアル通信云々の話はひとまず横に置いてしまい、
Chart コントロールの使い方をしっかり押さえてしまいましょう。
今の理解状況のまま、両方一緒に組み込もうとしても、
どちらの問題なのか切り分けしにくいと思いますよ。


> シリアル通信で送られてくるデータが常に変化しているので、
「データ」は変化しますが、「系列」は固定なのでは?

追加するのはデータ(Points)であって、系列(Series)では無いですよね。
両者の違いが曖昧になっているのではないでしょうか。


> 青い▪️は、"X-Axis"のデータで、オレンジの●は、"Y-Axis"のデータで間違いありません。
> Dim s = Chart1.Series.Add("X-Axis")
> Dim t = Chart1.Series.Add("Y-Axis")
上記の行を見れば、変数 s が Series 型であり、s.Name が "X-Axis" であることも
想像できますが、それを知っているのは開発した本人だけです。

最初の質問では、いきなり
> s.BorderWidth = 3
などと書かれているだけでしたから、s と t がそれぞれ
何を意図したものであるのかを第三者が想像することは困難です。

系列が明確に決まっているのであれば、こうした変数名に対しては
s ではなく「X-Axis のデータ系列」であることが分かるような名前、
t ではなく「Y-Axis のデータ系列」であることが分かるような名前を
付けることをおすすめします。


> データを「add」しなければと思っています。
データは追加するべきですが、系列はそうではないですよね。

まずは新規フォームを追加して、そこに Button と Chart だけを貼ってみてください。

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Chart1.Series.Add("Sample Series")
End Sub


上記を実行すると、1 回目のボタン押下では、凡例エリアに
新しい系列 "Sample Series" が追加されることになります。

しかし再度ボタンを押すと、同じ名称が競合しているために例外で止まってしまいます。
動的に Add する場合は、それぞれに異なる名前を指定せねばなりません。

しかし今回の系列数は不定ではなく、"X-Axis" と "Y-Axis" の 2 つだけであることが
あらかじめ決まっているようですし、プログラムで Add する必要すらないでしょう。
Form_Load 時(あるいはデザイン時)に Series を 2 つだけ用意しておき、
実行時にはそれを使うように書き換えてみてください。それで解決すると思います。


そもそも Chart には Series や Points など、いくつものコレクションがあるわけですが、
どうもそれぞれの違い(あるいは使い方)を理解しきれていないような印象を受けています。

先の回答ではその点を確認してみる(あるいは気付かせる)意味合いも兼ねて、
現状の設定内容を ListBox に出力させて確認させるよう促してみたのですが、
思惑が外れて、確認は後回しにされてしまったようですね…。


それはさておき、蛇足までにそれぞれのコレクションの意味を一応説明しておきますと。

➊【Chart1.ChartAreas】グラフエリア

上記では、一つの Chart コントロール上に、上下に 2 つの ChartArea が設定されています。
(各エリアの描画位置は自動的に調整されますが、明示的にエリアの座標を指定することも可能です)

データをどのエリアにプロットさせるのかは、Series の ChartArea プロパティで指定します。
.ChartAreas.Count が 0 であった場合、Chart コントロール上には何も描画されませんので、
ChartArea は最低一つは割り当てておくようにしてください。

これは通常、デザイン時に指定しておけば十分です。

vb素人さんが先月投稿されたコードでも、
>> Chart1.ChartAreas("ChartArea1")
のようにして、デザイン時に割り当てられた名前(といっても初期値のままですが)でアクセスしていましたよね。

他のコレクションも、同様に名前指定でアクセスできるようになっています。
また先の ListBox でのコードのように、ループ処理でそれらを列挙することもできます。


➋【Chart1.Legends】凡例

Series の Legend プロパティに Legend オブジェクトの名前を設定することで、
データ系統(Series)と凡例を関連付けます。

凡例を一切使わないのであれば、.Legends.Count が 0 の状態であっても構いません。
なお、複数の ChartAreas の凡例を一つの Legend 内ににまとめて表示することも、
一つのグラフの凡例を、複数の Legends に分散して配置することもできます。

これも、デザイン時に指定しておくことが多いです。
もちろんプログラムから作り直しても構いません。どちらにするかは好みの問題ですね。


➌【Chart1.Series】系列

上記では、色違いの 4 本の Series が登録されています。
系列をどのエリアにプロットさせるのかは、Series の ChartArea プロパティで指定します。

各系列は、Line、Spline、Column、Point といった、いずれかの ChartType を持ちます。
一つの CharArea 内に、ChartType の異なる複数の Series を含めることもできます。

たとえば Line グラフの場合、同じ系列上の各マーカーは線で繋がりますが、
異なる系列上のマーカーが繋がることはありません。



➍【Points】プロットするデータ
「Points」は Chart のプロパティではなく、Series のプロパティです。

今更説明するまでもないと思いますが、今回のケースでいえば、
受信したデータを動的に割り当てていく際に使うのがこれです。

今回の場合、前回のプロット結果を
>> Chart1.Series.Clear()   '既存の Series をクリアする場合 
で、自ら抹消してしまっていますよね。

その後で .Series.Add しなおしていますが、先の Clear によって、
それに従う Points も、当然一緒に失われてしまっているわけです。

今回、時間経過で増えていくのは「データ」であって「系列」ではありません。
.Points にデータを追加することはあっても、
.Series に系列を増やす必要なないですよね。

仮に、途中でグラフへの出力結果をクリアしたくなったとしても、
Clear するのは .Points だけで十分すよね。
投稿者 (削除されました)  () 投稿日時 2017/9/16 11:43:06
(削除されました)
投稿者 (削除されました)  () 投稿日時 2017/9/16 11:42:09
(削除されました)
投稿者 vb素人  (学生) 投稿日時 2017/9/16 08:45:29
魔界に仮面弁士さま

ありがとうございます。

>Received が呼ばれるたびに「Add」しているのは何か理由があるのでしょうか?
シリアル通信で送られてくるデータが常に変化しているので、受信するたびにデータを「add」しなければと思っています。

また、プロパティの設定は全てプログラムから行なっています。

青い▪️は、"X-Axis"のデータで、オレンジの●は、"Y-Axis"のデータで間違いありません。

>線を繋ぐには2つのデータが必要・・・
おっしゃる通りで、すみません。
Received の度にグラフにデータを表示しているので、Received の度に表示させているデータは1つしか無いことに気がつきました。だから見えない訳ですね。
本当に単純なことですみません。

ただ、今のプログラムからどのようにしてライン表示させれば良いのか?が分かっていません。

例えば、グラフ上に100個のデータをプロットさせるとして、Received 内で100個データを取得してからプロットするようにすれば良いのでしょうか。

投稿者 魔界の仮面弁士  (社会人) 投稿日時 2017/9/16 01:28:29
説明のため、アップ頂いた画像については、こちらで再掲させてもらいますね。


>> s と t を、それぞれどのように取得しているのかの説明が漏れていますよ…。
> timeは、Timerからカウントした値(時間)
> xとyは、シリアル通信で受信したデータです。

「time」や「x」や「y」のことを聞いているのではなく、
「s」や「t」のことを確認したかったのですが…。

> Dim s = Chart1.Series.Add("X-Axis")
> Dim t = Chart1.Series.Add("Y-Axis")
聞きたかったのはこれです。変数 s や t をどのように取得しているかという点です。

Series が何を表すオブジェクトであるかは把握されていると思うのですが、
Received が呼ばれるたびに「Add」しているのは何か理由があるのでしょうか?
デザイン時なり、Load 時なりに一度 Add してしまえば、
二回目以降はそれを使いまわすだけのはずですよね。

>> 画面に貼った後、初期設定からどのプロパティを変更したのかを教えてください。
> ③の部分のコードをすべて掲載します。
デザイン時画面で変更したプロパティは何一つなく、
プロパティの変更は、すべてプログラムから設定しているのでしょうか。


> 1つ目は、xとyのデータをいずれもPointグラフで表示させているときの状態です。


青い■が 変数 s の "X-Axis" 系統のデータ、
オレンジの●は、変数 t の "Y-Axis" 系統のデータなのですね?

それぞれ別系統のデータであり、そのどちらも、座標データは 1 つしか無い状態のようです。


> 2つ目は、xはライン、yはPointでグラフ表示させている状態です。


直線を引くには、始点と終点の 2 つの座標が必要ですよね?
「座標データが 1 つしか無い」現状では、線を描きようがないと思うのですが…。

X-Axis と Y-Axis は別系統のデータである以上、それらを繋ぐわけにもいきませんし。
投稿者 vb素人  (学生) 投稿日時 2017/9/15 16:50:43
画像を掲載させていただきます。

イメージを貼らせていただきます。
1つ目は、xとyのデータをいずれもPointグラフで表示させているときの状態です。

http://d.kuku.lu/1c6a87ce0b


2つ目は、xはライン、yはPointでグラフ表示させている状態です。
yのマーカーは見ることができますが、xのデータを見ることができません。
ただ、凡例にはxがラインで表示されています。

http://d.kuku.lu/e320abb474
投稿者 vb素人  (学生) 投稿日時 2017/9/15 16:44:28
魔界の仮面弁士さま

回答ありがとうございます。

>散布図というか、折れ線グラフのイメージでよいですか?
散布図と折れ線グラフの主な違いは、横軸上でのデータのプロット方法と思っています。
横軸をtimerでカウントした時間で表すことができれば、折れ線でも散布図でもどちらでも良いです。
シリアル通信で取得したデータ(マーカー)同士が線で接続されれば良いです。

>描画結果の画面イメージを貼ることはできますか?
すみません。画像の掲載方法が分かりません。


>s と t を、それぞれどのように取得しているのかの説明が漏れていますよ…。
すみません。回答があっているのか分からないのですが、
timeは、Timerからカウントした値(時間)
xとyは、シリアル通信で受信したデータです。


>画面に貼った後、初期設定からどのプロパティを変更したのかを教えてください。
③の部分のコードをすべて掲載します。


    Private Sub Received(x As UShort, y As UShort)

        If InvokeRequired Then
            Invoke(New Action(Of UShort, UShort)(AddressOf Received), x, y)
        Else

        xdata = 1023 - x
        ydata = 1023 - y
        Chart1.Series.Clear()   '既存の Series をクリアする場合 
        Dim s = Chart1.Series.Add("X-Axis")
        Dim t = Chart1.Series.Add("Y-Axis")

        Chart1.Legends(0).Font = New Font("MS Pゴシック", 10.0F, FontStyle.Bold)
        Chart1.Legends(0).BorderColor = Color.Black
        Chart1.Legends(0).BackColor = Color.Yellow
        Chart1.Legends(0).ShadowOffset = 4

        s.BorderWidth = 3
        s.BorderDashStyle = ChartDashStyle.Dot
        s.ChartType = SeriesChartType.Line
        t.ChartType = SeriesChartType.Point

        End If
    End Sub



>確認のためしばらく動かしてみた後で、下記のようにして
>チャートの内容を調べてみてはいかがでしょう。

すみません。こちらはまだ実施していません。

投稿者 魔界の仮面弁士  (社会人) 投稿日時 2017/9/15 11:33:26
> 横軸が、Timerでカウントする時間
散布図というか、折れ線グラフのイメージでよいですか?
https://support.office.com/ja-jp/article/4570a80f-599a-4d6b-a155-104a9018b86e


> グラフ上には何も表示がされません。(Point表示のseries2は表示されます。)
描画結果の画面イメージを貼ることはできますか?

どのような表示か連想しにくいのですが、
Legend のみが存在して、ChartArea が存在していない状態でしょうか?


> s.Points.AddXY(time, (1023 - x))
> t.Points.AddXY(time, (1023 - y))
s と t を、それぞれどのように取得しているのかの説明が漏れていますよ…。

Chart の設定がどうなっているのか分からないと話が伝わりにくいので、
画面に貼った後、初期設定からどのプロパティを変更したのかを教えてください。

また、CharArea が無くなっているとか、あるいは、そもそも
期待通りにデータを渡せていなかったということは無いでしょうか。
確認のためしばらく動かしてみた後で、下記のようにして
チャートの内容を調べてみてはいかがでしょう。


Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    ListBox1.BeginUpdate()
    ListBox1.Items.Clear()
    ListBox1.Items.Add("---Legends---")
    For Each lg In Me.Chart1.Legends
        ListBox1.Items.Add("[" & lg.Name & "]")
    Next
    ListBox1.Items.Add("---ChartAreas---")
    For Each ca In Me.Chart1.ChartAreas
        ListBox1.Items.Add("[" & ca.Name & "]")
    Next
    ListBox1.Items.Add("---Series---")
    For Each sr In Me.Chart1.Series
        ListBox1.Items.Add("[" & sr.Name & "]")
        ListBox1.Items.Add("凡例=" & sr.Legend)
        ListBox1.Items.Add("エリア=" & sr.ChartArea)
        ListBox1.Items.Add("データ数=" & sr.Points.Count.ToString("N0"))
        For Each p In sr.Points
            ListBox1.Items.Add(vbTab & p.XValue.ToString("R") & vbTab & p.YValues.FirstOrDefault().ToString("R"))
        Next
    Next
    ListBox1.Items.Add("---EndOfChart---")
    ListBox1.EndUpdate()
End Sub
投稿者 (削除されました)  () 投稿日時 2017/9/15 11:03:19
(削除されました)
投稿者 vb素人  (学生) 投稿日時 2017/9/15 08:52:21
シリアル通信から受信したデータを散布図に表示しています。
(Excelの散布図のようなグラフを描きたいと考えていて、Pointを選択しています。)
横軸が、Timerでカウントする時間
縦軸は、シリアル通信から得られるデータを考えています。

今の状況としては、
散布図のように点(マーカー)でプロットできている状況です。
散布図のように点(マーカー)と点(マーカー)を線で接続することができないかと思っています。

↓の投稿で魔界の仮面弁士さまに教えていただいて試してみているのですが、
http://rucio.cloudapp.net/ThreadDetail.aspx?ThreadId=30285


↓のプログラムの内容で実行すると、凡例の部分は、ラインで表示されているのですが、
グラフ上には何も表示がされません。(Point表示のseries2は表示されます。)

魔界の仮面弁士さまのコードと違う点は、
データが固定値なのか、シリアル通信で受信した値(受信するたびに変化)するかの違いだと思っています。

どうして何も表示されないのかがよく分かりません。
原因について教えていただけないでしょうか。


    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

'細かい所は省略します
         Dim datax As Byte() = {bin(2), bin(1)}
         Dim x As UShort = BitConverter.ToUInt16(datax, 0)    '符号なし2 バイト整数に変換 

         Dim datay As Byte() = {bin(4), bin(3)}
         Dim y As UShort = BitConverter.ToUInt16(datay, 0)    '符号なし2 バイト整数に変換 

         Received(x, y)

    End Sub


    Private Sub Received(x As UShort, y As UShort)

        If InvokeRequired Then
            Invoke(New Action(Of UShort, UShort)(AddressOf Received), x, y)
        Else

        xdata = 1023 - x
        ydata = 1023 - y

'☆
        s.BorderWidth = 3
        s.BorderDashStyle = ChartDashStyle.Dot
        s.ChartType = SeriesChartType.Line
t.ChartType = SeriesChartType.Point

        s.Points.AddXY(time, (1023 - x))
        t.Points.AddXY(time, (1023 - y))


        End If
    End Sub



    'データ数
    Dim CountData As Integer
    '測定間隔
    Dim time As Double = 0.0

    Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
        CountData = CInt(TextBox1.Text)
        Timer1.Interval = 100
        Timer1.Enabled = True
    End Sub


    Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
        CountData -= 1
        timeLabel.Text = "残り" & CountData & "個"
time+=0.1

        'Saveコード
        Dim sw As System.IO.StreamWriter
        sw = New System.IO.StreamWriter("accel.csv", True,
                                              System.Text.Encoding.GetEncoding(932))

        sw.WriteLine(xdata & "," & ydata & "," & time)
        sw.Close()

        If CountData = 0 Then
                Timer1.Enabled = False
                MessageBox.Show("測定終了")
            End If

    End Sub