プログラミング素人のはてなブログ

技術屋の末端。プログラミングも電気回路も専門外です。 コードに間違いなど見つけられたら、気軽にコメントください。 VC#、python3、ラズパイ始めました。

C#のグラフを使いこなす

C#GUIアプリでグラフを作成することができます。
しかし、このグラフ機能はいろんな機能がありますがなかなか情報が少ない(個人の見解です)ので、使いこなしには苦労しました。
グラフの種類もプロパティから変更すればExcelに匹敵するぐらい?ありますが、本記事では私がよく使うグラフ機能として、x-yグラフ(散布図)について整理しました。
f:id:s51517765:20180909214343g:plain

目次

chartオブジェクトの作成
系列データを配列で準備し指定
カーソルを追加
カーソルが示す系列を変更
表示する系列を変更
カーソルが指すグラフの数値を表示
横軸の表示範囲を設定


Chartオブジェクトの作成

まず、新規のプロジェクトとしてWindowsフォームアプリケーションを作成します。
ツールボックスから”Chart”を追加します。
追加されたグラフをクリックし、プロパティから”シリーズ”の”コレクション”を選択します。
グラフのChartTypeのドロップダウンリストから"ポイント"を選択します。
このなかにはマーカーの装飾もいろいろあります。
”データ”のnameで系列名を設定します。
これでExcelでいう散布図が作成されたと思います。
f:id:s51517765:20180909184644p:plain
(初期値では上図のような棒グラフになっています。)

系列データを配列で準備し指定

Form1をダブルクリックし、Form1.csを表示します。
Form1_Loadに以下のようなコードを記述すると、散布図をChart1およびChart2に作成できます。
Points.AddXY(x, y)のような形で、一つのプロットを作成できるので、それを配列の長さ分繰り返します。

        private void Form1_Load(object sender, EventArgs e)
        {
            double[] x1 = new double[100];
            double[] y1 = new double[100];
            double[] x2 = new double[100];
            double[] y2 = new double[100];

            for (int i = 0; i < 100; i++)
            {
                x1[i] = i * 0.5;
                y1[i] = Math.Sin((double)i / 20);
                y2[i] = i;
                y2[i] = Math.Sin((double)i / 10);

                chart1.Series["S1"].Points.AddXY(x1[i], y1[i]);
                chart2.Series["S2"].Points.AddXY(x2[i], y2[i]);
            }

f:id:s51517765:20180909184654j:plain

カーソルを追加

カーソルとは、グラフ上でポイントを示すためのものでC#では縦横のLineを作成することができます。
Chartのプロパティから”MouseMove”のイベントを作成します。
f:id:s51517765:20180909184706j:plain

できたMouseMoveのなかに以下のようなコードを作成すると、ポインタに合わせてカーソルが動きます。

        private void chart1_MouseMove(object sender, MouseEventArgs e)
        {
            Point mousePoint = new Point(e.X, e.Y);
            chart1.ChartAreas[0].CursorX.SetCursorPixelPosition(mousePoint, false);
            chart1.ChartAreas[0].CursorY.SetCursorPixelPosition(mousePoint, false);
            chart1.ChartAreas[0].CursorX.Interval = 0.001;
            chart1.ChartAreas[0].CursorY.Interval = 0.001;
        }

"Interval"を設定しないと、カーソルの刻みが1になってしまうので、動きがカクカクになってしまいます(この場合、特に縦)。
そこで適切な刻みを設定します。ここではX,Yとも0.001にしました。
これで、カーソルは滑らかに動きますが、これではマウスポインタについてくるだけで、グラフ上で特別な意味を持ちません。

マウスポインタの指すXに対する、グラフのYを指すようにしたいですね。

そのためには、カーソルの位置をグラフ上のxの値に変換AxisX.PixelPositionToValue(e.X)し、このもとめたxの値からyの値を算出しCursorYの位置を設定します。

        private void chart1_MouseMove(object sender, MouseEventArgs e)
        {
            chart1.ChartAreas[0].CursorX.Interval = 0.001;
            chart1.ChartAreas[0].CursorY.Interval = 0.001;
            try
            {
                double x = chart1.ChartAreas[0].AxisX.PixelPositionToValue(e.X);
                double y = Math.Sin((double)x / 0.5 / 20);
                chart1.ChartAreas[0].CursorX.Position = x;
                chart1.ChartAreas[0].CursorY.Position = y;
            }
            catch
            {
                //pass
            }
        }

x,yとも今回の例のような数式で表せるとは限らないし、csvからデータを読み込んで配列として使いたい場合もあります。
そのときは、カーソルのxに相当するx[index]をさがして、同じindexのyを探してやるという方法ができます。

        private void chart1_MouseMove(object sender, MouseEventArgs e)
        {
            chart1.ChartAreas[0].CursorX.Interval = 0.001;
            chart1.ChartAreas[0].CursorY.Interval = 0.001;
            try
            {
                int x = (int)chart1.ChartAreas[0].AxisX.PixelPositionToValue(e.X);

                int index=0;
                for (int i = 0; i < y1.Length; i++)
                {
                    if (x - x1[i] < 0.1) break; //取得した座標(x)に対して近い配列の要素をさがし、そのindexを取得する 
                    index = i;
                }

                chart1.ChartAreas[0].CursorX.Position = x;
                chart1.ChartAreas[0].CursorY.Position = y1[index];
            }
            catch
            {
                //pass
            }
        }

カーソルが示す系列を変更

radiobuttonでカーソルが示すデータ系列を変更できるようにします。
あらかじめ、Chart1に系列を追加しておきます。
radiobuttonとは、いずれか一つを選択するためのツールですので、この状態を読み取って
chart1.ChartAreas[0].CursorY.Position = y1[index];にあたえるyの値を変更します。
カーソルの色も系列に合わせると視認性が高まります。

             chart1.ChartAreas[0].CursorX.Position = x;
                if (radioButtonS1.Checked)
                {
                    chart1.ChartAreas[0].CursorY.Position = y1[index];
                    chart1.ChartAreas[0].CursorX.LineColor = Color.Red;
                    chart1.ChartAreas[0].CursorY.LineColor = Color.Red;
                }
                else if (radioButtonS2.Checked)
                {
                    chart1.ChartAreas[0].CursorY.Position = y2[index];
                    chart1.ChartAreas[0].CursorX.LineColor = Color.Aqua;
                    chart1.ChartAreas[0].CursorY.LineColor = Color.Aqua;
                }

表示する系列を変更

チェックボックすの状態によって、グラフの表示有無を変更しようとするときは、非表示にしたい系列の表示colorをTransparent(透明)にします

        private void checkBox1_CheckedChanged(object sender, EventArgs e)
        {
            if (checkBox1.Checked)
            {
                chart1.Series["S1"].MarkerColor = Color.Red;
            }
            else
            {
                chart1.Series["S1"].MarkerColor = Color.Transparent;
            }
        }

        private void checkBox2_CheckedChanged(object sender, EventArgs e)
        {
            if (checkBox2.Checked)
            {
                chart1.Series["S2"].MarkerColor = Color.Aqua;
            }
            else
            {
                chart1.Series["S2"].MarkerColor = Color.Transparent;
            }
        }

カーソルが指すグラフの数値を表示

グラフの数値が細かいときはその数値が「見える」ようにすると扱いやすいです。

 label1.Text = "S1= " + y1[index].ToString("0.00");

f:id:s51517765:20180909184722j:plain

横軸の表示範囲を設定

グラフの表示を拡大したいことありますよね。
ここでは、現在の表示範囲の最大値に対してボタンクリックで表示範囲を比例で大小させるようにしました。

        private void buttonExp_Click(object sender, EventArgs e)
        {
            double maximum_C2 = chart2.ChartAreas[0].AxisX.Maximum; //現在の最大値を取得
            chart2.ChartAreas[0].AxisX.Maximum = 0.5 * maximum_C2;
        }

        private void buttonShrink_Click(object sender, EventArgs e)
        {
            double maximum_C2 = chart2.ChartAreas[0].AxisX.Maximum;
            chart2.ChartAreas[0].AxisX.Maximum = 2 * maximum_C2;
        }

直接数値で設定することもできます。

 chart2.ChartAreas[0].AxisX.Minimum = 0;
 chart2.ChartAreas[0].AxisX.Maximum = 40;

グラフに入力するデータ範囲が変動するときあえて、表示範囲を固定しておくという使い方もできます。
Form1_Loadで初期化時に呼び出しておくとよいです。

しかし、これだと0基準に拡大になるので、0付近を見たいときはいいですが、そうでないときはScaleViewを使います。

        private void buttonExp_Click(object sender, EventArgs e)
        {
            double maximum_C2 = chart2.ChartAreas[0].AxisX.Maximum; //現在の最大値を取得
            viewRate *= 0.5;
            chart2.ChartAreas[0].AxisX.Interval = 10;
            chart2.ChartAreas[0].AxisX.ScaleView.Size = maximum_C2*viewRate;
        }

        private void buttonShrink_Click(object sender, EventArgs e)
        {            
            double maximum_C2 = chart2.ChartAreas[0].AxisX.Maximum;
            viewRate *= 2;
            chart2.ChartAreas[0].AxisX.ScaleView.Size = maximum_C2 * viewRate;
        }

まとめ

C#のグラフ機能について整理しました。
これだけ使いこなせればかなり便利だと思います。

参考

ゴールからはじめるC# ~「作りたいもの」でプログラミングのきほんがわかる

ゴールからはじめるC# ~「作りたいもの」でプログラミングのきほんがわかる

Visual C#(C#によるGUIアプリ)の基本的なところ、を習得するのにちょうどいいと思います。

GIF動画アプリ

実ははてなブログではGIF動画を張り付けることが出来るんです。
冒頭の動画はこちらのアプリで作成しました。
www.screentogif.com