時間ごとにデータを保存する方法 への返答

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

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

投稿者 vb素人  (学生) 投稿日時 2017/9/15 08:47:57
魔界の仮面弁士さま

ありがとうございます。
「時間ごとにデータを保存する」については解決することができましたので、このスレッドは解決とさせていただきます。

別スレッドで散布図のデータをラインで結ぶについて質問させていただきたいと思います。
投稿者 (削除されました)  () 投稿日時 2017/9/14 10:06:00
(削除されました)
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2017/9/12 14:41:48
質問内容が、タイトルの「時間ごとにデータを保存する方法」とかけ離れて来たので、
過去ログを分かりやすくするためにも、別の質問としてスレッドを立て直すことをお奨めします。


> Excelの散布図のようなグラフを描きたいと考えていて、Pointを選択しました。
> 散布図のように点(マーカー)と点(マーカー)を線で接続することができないかと思っています。

なるほど了解です。

そもそも元データからどういうグラフを描きたかったのかなどが
特に述べられていなかったので、何故 Point を選択したのか
分からなかったのですが、何となくイメージできました。

元質問では、ライン表示がどういうものをイメージしていたのか分かりませんでしたが、
Excel の「近似曲線」の意味ではなく、マーカーを単純に直線で繋ぐだけで良いのですね。


> これがLineなどでできるということでしょうか。

たとえば下記を見てください。

青紫な太線の S2 系統は、「Point グラフ+独自描画ライン」による実装、
もうひとつのカラフルな S1 系統は、「Line グラフ」による実装です。

なお、上部に置かれた ComboBox は、S1 系統の ChartType を変更するためのオマケです。



Option Strict On
Imports System.Windows.Forms.DataVisualization.Charting
Public Class Form1
    Private WithEvents Chart1 As Chart
    Private WithEvents ComboBox1 As ComboBox
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Chart1 = New Chart() With {.Dock = DockStyle.Fill}
        Controls.Clear()
        Controls.Add(Chart1)

        ComboBox1 = New ComboBox() With {.Dock = DockStyle.Top}
        ComboBox1.DropDownStyle = ComboBoxStyle.DropDownList
        ComboBox1.DataSource = [Enum].GetValues(GetType(SeriesChartType))
        Controls.Add(ComboBox1)

        Chart1.Series.Clear()
        Chart1.ChartAreas.Clear()

        Dim ca = Chart1.ChartAreas.Add("CA")
        Dim s1 = Chart1.Series.Add("S1")
        Dim s2 = Chart1.Series.Add("S2")

        s1.BorderWidth = 3
        s1.BorderDashStyle = ChartDashStyle.Dot
        s1.ChartType = SeriesChartType.Line
        s2.MarkerSize = 15
        s2.MarkerStyle = MarkerStyle.Circle
        s2.MarkerColor = Color.BlueViolet
        s2.ChartType = SeriesChartType.Point
        s2.BorderDashStyle = ChartDashStyle.Solid

        ComboBox1.SelectedItem = s1.ChartType
        AddHandler ComboBox1.SelectionChangeCommitted, Sub() s1.ChartType = DirectCast(ComboBox1.SelectedItem, SeriesChartType)

        s2.Points.AddXY(3, 5)
        s2.Points.AddXY(4, 55)
        s2.Points.AddXY(5, 25)
        s2.Points.AddXY(1, 70)

        s1.Points.AddXY(1, 10)
        s1.Points.AddXY(3, 30)
        s1.Points.AddXY(2, 10)
        s1.Points.AddXY(2, 40)
        s1.Points.AddXY(4, 80)
        s1.Points.AddXY(3, 80)


        s1.Points(0).Color = Color.OrangeRed
        s1.Points(1).Color = Color.ForestGreen
        s1.Points(2).Color = Color.DeepPink
        s1.Points(3).Color = Color.DarkCyan
        s1.Points(4).Color = Color.DarkGoldenrod
        s1.Points(5).Color = Color.Aqua

        s1.Points.ToList().ForEach(Sub(p) p.MarkerSize = 21)

        s1.Points(0).MarkerStyle = MarkerStyle.Square
        s1.Points(1).MarkerStyle = MarkerStyle.Cross
        s1.Points(2).MarkerStyle = MarkerStyle.Diamond
        s1.Points(3).MarkerStyle = MarkerStyle.Star5
        s1.Points(4).MarkerStyle = MarkerStyle.Triangle
        s1.Points(5).MarkerStyle = MarkerStyle.Star10
    End Sub

    Private Sub Chart2_PrePaint(sender As Object, e As ChartPaintEventArgs) Handles Chart1.PrePaint
        Dim cg = e.ChartGraphics
        Dim pre As PointF? = Nothing
        Using myPen As New Pen(Brushes.BlueViolet, 4)
            myPen.DashStyle = Drawing2D.DashStyle.Dash
            For Each p In Chart1.Series("S2").Points
                Dim x = cg.GetPositionFromAxis("CA", AxisName.X2, p.XValue)
                Dim y = cg.GetPositionFromAxis("CA", AxisName.Y2, p.YValues(0))
                Dim cur = cg.GetAbsolutePoint(New PointF(CSng(x), CSng(y)))
                If pre IsNot Nothing Then
                    e.ChartGraphics.Graphics.DrawLine(myPen, pre.Value, cur)
                End If
                pre = cur
            Next
        End Using
    End Sub
End Class
投稿者 (削除されました)  () 投稿日時 2017/9/12 14:39:04
(削除されました)
投稿者 vb素人  (学生) 投稿日時 2017/9/12 10:27:57
魔界の仮面弁士さま


私の確認については、
魔界の仮面弁士さまがおっしゃるとおり、
>VB6 時代の、ActiveX 版 MSChart コントロールでは、
> Dim Series As MSChart20Lib.Series
> Set Series = MSChart1.Plot.SeriesCollection(0)
> Series.ShowLine = True
>のようなコードを書くことがありましたが、それと勘違いしていたということはないですか?

これと勘違いしていました。

そして、Pointを選んだ理由についてですが、

Excelの散布図のようなグラフを描きたいと考えていて、Pointを選択しました。
横軸が、Timerでカウントする時間
縦軸は、シリアル通信から得られるデータを考えています。

質問が2つあります。
1つ目は、ライン表示について。
今の状況としては、
散布図のように点(マーカー)でプロットできている状況です。
散布図のように点(マーカー)と点(マーカー)を線で接続することができないかと思っています。

これがLineなどでできるということでしょうか。


2つ目は、Pointのグラフについて。

0.0を指定して、グラフ上にマーカーを表示させたときに、
X軸だけ1の位置にマーカーが表示されてしまいます。
s.Points.AddXY(0, 0)

X軸の値(例えば最大値)を変えても必ず1の所にデータがプロットされてしまいます。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2017/9/11 17:09:29
> 調べてみたところ、↓の設定で以前はライン表示ができていたようですが、

確認してみたいので、どこで調べたのか、情報の出所を教えてください。
書籍名とか、Webサイトの URL とか…。


> 私の今の環境(vb2013)にはなさそうです。
> series.ShowLine = True
series というのが、
 Dim Series As System.Windows.Forms.DataVisualization.Charting.Series = Me.Chart1.Series(n)
あるいは
 Dim Series As System.Windows.Forms.DataVisualization.Charting.SeriesCollection = Chart1.Series
の事だとしたら、そこに ShowLine というプロパティは無いはずです。
2013 はおろか、他のバージョンであっても。

VB6 時代の、ActiveX 版 MSChart コントロールでは、
 Dim Series As MSChart20Lib.Series
 Set Series = MSChart1.Plot.SeriesCollection(0)
 Series.ShowLine = True
のようなコードを書くことがありましたが、それと勘違いしていたということはないですか?



> ②xと時間、yと時間をグラフに表示する。(グラフタイプは、point)
> ②のグラフ表示の時に、「ライン」で表示させたいと思っています。

グラフタイプというのは、ChartType プロパティに渡す SeriesChartType 列挙体のことですよね。

ラインで表示させたいのであれば、Point ではなく、Line/FastLine/StepLine あたりを
選択するべきだと思いますが、なぜ Point を選択されたのでしょうか?
http://hanatyan.sakura.ne.jp/chart/chart1.htm
投稿者 vb素人  (学生) 投稿日時 2017/9/11 10:25:14
魔界の仮面弁士さま

ありがとうございます。
変数の設定に誤りがありました。
修正したら、時間を加算することができました。

①シリアルデータから受信したx,yのデータと時間を保存する。
②xと時間、yと時間をグラフに表示する。(グラフタイプは、point)

ここまでできていますが、
②のグラフ表示の時に、「ライン」で表示させたいと思っています。


調べてみたところ、↓の設定で以前はライン表示ができていたようですが、
私の今の環境(vb2013)にはなさそうです。

series.ShowLine = True

私の調べ方が足りないのかもしれませんが、ライン表示はできないのでしょうか。

投稿者 魔界の仮面弁士  (社会人) 投稿日時 2017/9/8 17:21:43
以前の指摘を繰り返すのは疲れたので、細かい所は目をつぶるとして:

> 保存されるtimeは0.1のまま変化がありません。
ローカル変数とフィールド変数を間違えているとか?
投稿者 vb素人  (学生) 投稿日時 2017/9/8 16:32:22
魔界の仮面弁士さま

ありがとうございます。
教えていただいた方法でトライしてみました。

まず、グローバル変数を用意して、
    Private xdata As Double
    Private ydata As Double

シリアル通信で受信したデータをグローバル変数に渡します。(☆の部分)



    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

        End If
    End Sub


このあと、↓の④でなんとなくできました。

↓の内容ですが、TextBoxでデータを保存する数を指定しています。
その後、0.1秒おきにタイマーイベントを発生させて、残りのデータが0になったらファイルへの書き込みを終了させます。


    Dim CountData As Integer


    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 = CountData - 1
        timeLabel.Text = "残り" & CountData & "個"

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

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


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


これで時間ごとにデータを保存することはできました。

次のステップとして、
①時間もファイルに書き込む
②時間を横軸としてx,yのデータをグラフに表示させる(オシロスコープみたいなイメージ)

①についてTimer1.Tickの中で、0.1秒ずつ加算していけばできると思い、

        Dim time As Double
        time += 0.1

を書いてみたのですが、保存されるtimeは0.1のまま変化がありません。
初歩的な間違いだと思うのですが、教えていただけると助かります。

投稿者 魔界の仮面弁士  (社会人) 投稿日時 2017/9/8 09:32:10
いろいろなやり方があるとは思いますが、
たとえばこんな手順的は如何でしょう。(サンプルコードは掲載しないでおきます)

(1) ローカル変数に受信した X, Y データを、フィールド変数に保持しておきます。
 受信処理が複数回行われた場合に備え、コレクションで管理する必要がある場合は、
 Queue(Of ) などを利用できるかと思います。
 ※ Invoke / BeginInvoke は使いません。

(2) Timer1_Tick が呼ばれたら、(1) で保存していたデータを取り出して
 ファイルに保存します。この保存の方法としては:
  <A案> ファイルには「追記」モードで記述し、書き込み終わった分は(1)から取り除く。
  <B案> ファイルは毎回「新規作成」し、今まで受信した分すべてを毎回作り直す。
 などのパターンがあります。


なお、データ受信のスレッドと、そのデータを利用する(この場合はファイル出力)スレッドが
異なっていますので、何らかの同期制御も必要になります。
(たとえば SyncRoot プロパティを SyncLock するなど)
投稿者 daive  (社会人) 投稿日時 2017/9/6 12:36:37
なにやら、基礎が出来ていないのに、
先へ進もうとしていますが、崩壊パターンです。

1.モジュール化、クラス化、なぜオブジェクトモデルなのか?とか学習してください。
2.今回のモノであれば、 ざっくり、通信系、ロガー系、トレンド系、帳票系
  と共通機能、などへ機能分割すべきです。
3.通信系は、通信、エラー処理、リングバッファへの書き込み(タイムスタンプ込み)
4.ロガー系は、指定周期でファイルへの書き出し、エラー処理
5.トレンド系、帳票系の前に、ファイルを扱う共通部分や、その他共通部分を考え作り、デバッグ

以上おせっかいです。
投稿者 vb素人  (学生) 投稿日時 2017/9/6 09:37:25
シリアル通信で受信したデータの保存について。

↓の①~③のコードで受信データxとyの保存まで対応できていますが、
自分で設定した時間(TextBox1で設定)ごとにxとyのデータを保存する方法が分かりません。





    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
  'グラフの設定などは省略
            s.Points.AddXY(x, y)

            '★
            Dim sw As System.IO.StreamWriter
            sw = New System.IO.StreamWriter("abc.csv", True,
                                                  System.Text.Encoding.GetEncoding(932))

            sw.WriteLine(x & "," & y)
            sw.Close()

        End If
    End Sub


Button3を押したらデータの保存を開始して、
Timer1_Tick内(☆印の箇所?)でxとyをファイルに書き込むのかと思っていますが、
xとyのデータを受け渡す方法が上手くいきません。



    Dim CountTimer As Integer

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



    Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
        CountTimer = CountTimer - 1
'☆
        If CountTimer = 0 Then
            Timer1.Enabled = False
            MessageBox.Show("測定終了")
        End If
    End Sub