Visual C++による簡単なグラフィック描画
ロジスティック方程式(カオス)のシミュレーション
ドットネットのVisual C++を用いるとグラフィック描画が簡単にできる。スチュアート(著)・水谷(訳)「世界を変えた17の方程式」(2013)に説明されているロジスティック方程式によるカオスのシミュレーションプログラムを作成してみた。このプログラムを実行すると図1のようにランダムに見える折れ線が描画される。
図1
図1の画面の左上は図2のようになっている。
図2
「k=」の下に設定されているkの値4は、ロジスティック方程式
におけるの値である。「x0
=」の下の値0.7は(1)式におけるの値である。これらの値を変えると図1の折れ線の様子も変わる。図3は
図3
図4のように、、と設定してから「描画」ボタンをクリックして描いたものである。
図4
の値が3より大きいとき、図1のような不安定な折れ線になると説明されている。の値は時点における当該個数の総数に対する割合を表しているので
の範囲の数であるが、
であるので、の値が4より大きくなると、
となりうる。このときは
とおいている。
プログラムはプログラム名SimuLgstcEq.exeをクリックしてダウンロード・実行できる。実行開始時に「***.dllがインストールされていません」というようなエラーメッセージが表示された場合は、Visual Studio Express 2012 for Windows Desktopを
http://www.microsoft.com/visualstudio/jpn/downloads
からダウンロードインストールすれば(2013.10.15現在)、必要なファイルがインストールされて、プログラムSimuLgstcEq.exeもダウンロードして直接実行できるようになる。
ドットネットのVisual C++による描画は、グラフィックスハンドルgを
pictureBox1->Image = gcnew Bitmap(pw, ph);
g = Graphics::FromImage(pictureBox1->Image);
と用意すれば、gを用いてpictureBox1のImage上に描画ができて、図1のように表示される。
点(a, b)から点(c, d)まで線分をペンpnBkによって引くことは、次の文
g->DrawLine( pnBk,a, b, c, d );
の実行によってできる。
ドットネットのVisual C++(Visual C++/CLR)で描画を行うためのプログラムはWindows Form Applicationとして作成するが、このタイプのプログラムの新規作成法については<簡単なVisual C++ フォーム アプリケーション プログラミング>で説明している。
新規作成したフォームアプリケーションプログラムのフォームにpictureBoxなどを張り付けて用意する方法については<岡本安晴「大学生のための心理学VC++プログラミング入門」勁草書房>を参照されたい。
図1の折れ線は、
Xt = Xtp1;
Xtp1 = vk * Xt * (1.0 - Xt);
if(Xtp1 > 1) Xtp1 = 1.0;
によって、値XtとXtp1を更新して、次の関数
g->DrawLine( pnBk, xpos(ti / 100.0), ypos(Xt), xpos((ti + 1.0) / 100.0), ypos(Xtp1) );
によって、点(ti/100, Xt)と点((ti+1)/100, Xtp1)を線分でつなぐことにより描いている。このとき、タイマーtimerを用いて、更新の時間間隔を
timer1->Interval = 100;
と設定している。この時間間隔をおいてタイマーイベントハンドラーtimer1_Tickが呼び出され、折れ線が1本ずつ描かれるので、折れ線の描かれていく様子を観察することができる。タイマーを用いずに時間間隔をおかず折れ線を続けて描くと、すべての折れ線が瞬時に描かれ表示される。
タイマーによって時間間隔をおいて描画するとき、グラフィックスハンドルは時間がある程度経過すると、描画結果が画面に反映されなくなる。そのため、描画の開始時に次の文
pictureBox1->Image = pictureBox1->Image;
を実行している。この代入文を実行すると、その後のグラフィックスハンドルによる描画が画面に反映されるようになる。
描画が短時間で終了せず、時間を空けてグラフィックスハンドルgが用いられるときは、時間経過後はgによる描画処理が画面に反映されないということが起こるので、このような場合は上のImageへの代入を行っておくとよい。
プログラムのソースコードを、以下に示す。
int ph, pw; // pictureBoxの高さと幅
int x0, y0, x1, y1; // 原点(0, 0)と点(1, 1)pictureBox1->Image上での位置(ピクセル単位)
double Xt, Xtp1, vk; // Xtp1 = vk * Xt * (1.0 - Xt)
int ti; // 離散時点
//
// 0 < x < 1 を x0 < xpos < x1 に変換
//
int xpos( double x ){
return int(x0 + x * (x1 - x0));
}
//
// 0 < y < 1 を y0 < ypos < y1 に変換
//
int ypos( double y ){
return int(y0 - y * (y0 - y1));
}
Graphics ^g; // 描画ハンドル
private: System::Void MyForm_Load(System::Object^ sender, System::EventArgs^ e) {
timer1->Enabled = false; // プログラムの実行開始時はtimerを止めておく。
//
// ウィンドウの最大化
//
this->WindowState = System::Windows::Forms::FormWindowState::Maximized;
Update();
//
// pitureBoxの位置と大きさの設定
//
pictureBox1->Top = 0;
pictureBox1->Left = buttonDraw->Left + buttonDraw->Width + 1;
pictureBox1->Width = this->ClientSize.Width - pictureBox1->Left;
pictureBox1->Height = this->ClientSize.Height;
pw = pictureBox1->Width;
ph = pictureBox1->Height;
//
// Image と 描画ハンドル g の設定
//
pictureBox1->Image = gcnew Bitmap(pw, ph);
g = Graphics::FromImage(pictureBox1->Image);
//
// 描画位置の基準点(x0, y0)、(x1, y1)の設定
//
x0 = int(pw * 0.05);
y0 = int(ph * 0.8);
x1 = int(pw * 0.95);
y1 = int(pw * 0.1);
DrawCurve(); // 描画
}
private: System::Void buttonClose_Click(System::Object^ sender, System::EventArgs^ e) {
this->Close();
}
//
// 描画関数
//
void DrawCurve(){
pictureBox1->Image = pictureBox1->Image; // 描画ハンドル g による描画が有効になるようにする
//
// 描画領域を白で塗り潰す
//
Brush ^ bshWt = gcnew SolidBrush( Color::White );
g->FillRectangle( bshWt, 0, 0, pw, ph );
//
// グラフの描画枠
//
Pen ^ pnBk = gcnew Pen(Color::Black, 2);
array<Point> ^ xy = {Point(xpos(0.0), ypos(0.0)), Point(xpos(1.0), ypos(0.0)),
Point(xpos(1.0), ypos(1.0)), Point(xpos(0.0), ypos(1.0)),
Point(xpos(0.0), ypos(0.0)) };
g->DrawLines( pnBk, xy);
//
// 描画枠の座標値
//
// 縦軸の1
String ^ str = "1";
using System::Drawing::Font;
Font ^ fnt = gcnew Font("MS ゴシック", ypos(0.0) - ypos(0.05), GraphicsUnit::Pixel);
Brush ^ bshBk = gcnew SolidBrush(Color::Black);
SizeF sizeStr = g->MeasureString( str, fnt );
g->DrawString( str, fnt, bshBk, xpos(-0.005) - sizeStr.Width, ypos(1.0)-sizeStr.Height/2 );
// 縦軸の0
str = "0";
sizeStr = g->MeasureString( str, fnt );
g->DrawString( str, fnt, bshBk, xpos(-0.005) - sizeStr.Width, ypos(0.0)-sizeStr.Height/2 );
// 横軸の0
g->DrawString( str, fnt, bshBk, xpos(0.0) - sizeStr.Width/2, ypos(-0.01) );
// 横軸の100
str = "100";
sizeStr = g->MeasureString( str, fnt );
g->DrawString( str, fnt, bshBk, xpos(1.0) - sizeStr.Width/2, ypos(-0.01) );
//
// 初期値をXtp1に、係数をvkに設定
//
Xtp1 = double::Parse(textBoxX0->Text);
vk = double::Parse(textBoxK->Text);
// 時刻 ti の初期化
ti = 0;
//
// timerの時間間隔の設定と起動
//
timer1->Interval = 100;
timer1->Enabled = true;
}
//
// 「描画」ボタンのクリックで実行
//
private: System::Void buttonDraw_Click(System::Object^ sender, System::EventArgs^ e) {
DrawCurve(); // 描画開始
}
//
// timerイベントで呼び出される関数
//
private: System::Void timer1_Tick(System::Object^ sender, System::EventArgs^ e) {
timer1->Enabled = false; // timerを止める
//
// ti が100より小さいとき、Xtp1の値を更新して描画
//
if (ti < 100){
//
// 値の更新と描画
//
Xt = Xtp1;
Xtp1 = vk * Xt * (1.0 - Xt);
if(Xtp1 > 1) Xtp1 = 1.0;
Pen ^ pnBk = gcnew Pen(Color::Black, 2);
pictureBox1->Image = pictureBox1->Image;
g->DrawLine( pnBk, xpos(ti / 100.0), ypos(Xt), xpos((ti + 1.0) / 100.0), ypos(Xtp1) );
ti++;
timer1->Enabled = true; // timerを有効にして、次の更新と描画が行われるようにする
}
}
プログラムを圧縮ファイルとまとめたものはSimuLgstcEq.zipとして用意した。圧縮ファイル名SimuLgstcEq.zipをクリック・ダウンロードして解凍すればVisual Studio 2012で開くことができる。