HTML5 Canvasで写像ダイアグラム(写像図)を描く
HTML5 CanvasとJavaScriptを用いてHTMLドキュメントに数学の写像ダイアグラム(写像図)を描いてみた。この投稿はその備忘録。
写像ダイアグラムを描きたいHTMLドキュメント内の場所にcanvas要素を埋め込む必要がある。そのcanvas要素は例えば次のように記す。その際にid属性を指定することを忘れずに。HTML5 Canvasはこのidを受け取る。
<canvas id="mapping" width="200" height="200">
HTML5 CanvasかJavaScriptに未対応か無効になっています。
</canvas>
canvas要素のwidthとheightという属性はキャンバスの幅と高さを指定するもの。このサイズを適切に指定しないと描かれたオブジェクトがはみ出して見えなくなってしまうことがある。
楕円オブジェクト
HTML5 Canvasを使って楕円を描くためにはellipse()というメソッドを用いることができる。このメソッドの引数は次の順番に指定する。オプションは省略することができる。
ellipse(1,2,3,4,5,6,7,8);
- 楕円の中心の水平座標を指定。
- 楕円の中心の垂直座標を指定。
- 楕円の水平軸半径を非負の値で指定。
- 楕円の垂直軸半径を非負の値で指定。
- 楕円の水平軸の傾きを弧度法で指定。
- パスの始点を弧度法で指定。
- パスの終点を弧度法で指定。
- 【オプション】楕円を描くパスの方向を反時計回りにしたい場合にtrueと指定。
ただしellipse()メソッドは古いウェブブラウザでは対応していない場合があるので要注意。
次のJavaScriptのコードではコンストラクタ函数によって楕円クラスのようなものを定義している。そしてそれをインスタンス化して各々の楕円オブジェクトを作成し、コンストラクタ函数の中で定義した描画メソッドを呼び出すことで楕円を描くようにしている。
<script>
// 楕円のコンストラクタ函数を定義
function ellipse(radiusX,radiusY,rotation,
lineWidth,stColor,flColor){
// プロパティを定義
this.radiusX = radiusX; // 楕円の水平軸半径
this.radiusY = radiusY; // 楕円の垂直軸半径
this.rotation = rotation; // 楕円の水平軸の傾き
this.startAngle = 0; // 線の始点
this.endAngle = Math.PI / 180 * 360; // 線の終点
this.lineWidth = lineWidth; // 線の太さ
this.stColor = stColor; // 線の色
this.flColor = flColor; // 塗りつぶす色
// 楕円を描くメソッドを定義
this.draw = function(canvasId,x,y){
// HTML5 Canvasを利用する
const Canvas = document.getElementById(canvasId)
const Ctx = Canvas.getContext("2d");
Ctx.beginPath(); // パスの初期化
// ellipse()メソッドを利用
Ctx.ellipse(x,y,this.radiusX,this.radiusY,
this.rotation,this.startAngle,this.endAngle);
Ctx.lineWidth = this.lineWidth; // 線の太さ
Ctx.strokeStyle = this.stColor; // 線の色
Ctx.stroke(); // 線を描く
Ctx.fillStyle = this.flColor; // 塗りつぶす色
Ctx.fill(); // 塗りつぶす
}
}
// 各々のオブジェクトを作成(インスタンス化)
const Ellipse1 = new ellipse(50,80,0,2,'blue','skyblue');
const Ellipse2 = new ellipse(50,80,0,2,'red','pink');
// 定義したメソッドを呼び出して楕円を描画
Ellipse1.draw(mapping,60,110);
Ellipse2.draw(mapping,190,110);
</script>
コンストラクタ函数を利用してクラスのようなものを定義することができる。コンストラクタ函数が普通の函数と違うところは「this.変数名」によって変数を宣言しているところ。これによって「コンストラクタ函数名.変数名」という書式でコンストラクタ函数の外からアクセスできるようになる。これはすなわちオブジェクト指向言語のプロパティ(メンバ変数)のように機能する。プロパティに値を代入するには「コンストラクタ函数名.変数名 = リテラル;」という書式を使う。
コンストラクタ函数は擬似的なクラスとして働くので、インスタンス化によって複数のオブジェクトをこれに基づいて作成することができる。そのためにはnewというキーワードを使って変数に代入する。
コンストラクタ函数内では「this.メソッド名 = function(引数){...}」という書式を用いてメソッド(メンバ函数)のようなものを定義することができる。これも「コンストラクタ函数名.メソッド名();」という書式を用いてコンストラクタ函数の外から呼び出すことができる。
楕円を定義したコンストラクタ函数では、それがインスタンス化されて各々のオブジェクトが作られるときに、楕円の中心点の水平座標と垂直座標、楕円の水平軸の傾き(ラジアン)、線の太さ、線の色、そして塗りつぶす色を引数として受け取るように定義した。
楕円を定義したコンストラクタ函数の中で定義したdrawメソッドは、HTML5 Canvasによって楕円を描画するためのもの。このメソッドではそれが呼び出される際にcanvas要素のIDと楕円の中心点の座標を受け取るようにした。
楕円を定義したコンストラクタ函数の中で「線の終点」を示すプロパティに代入されている「Math.PI / 180 * 360」という計算は、度数法の値360を弧度法の値に変換するためのもの。HTML5 Canvasのほうのellipse()メソッドは角度をラジアンで受け取るため。
ラベル・オブジェクト
ラベルのオブジェクトを定義するコンストラクタ函数が次のコード。
<script>
// 楕円のコンストラクタ函数を省略
// ラベルのコンストラクタ函数を定義
function lavel(text,color){
// プロパティを定義
this.text = text; // 文字列
this.color = color; // 文字列の色
// ラベルを描くメソッドを定義
this.draw = function(canvasId,x,y){
// HTML5 Canvasを利用する
const Canvas = document.getElementById(canvasId);
const Ctx = Canvas.getContext("2d");
// フォントを設定
Ctx.font = '15px sans-serif';
// 文字の水平位置を設定
Ctx.textAlign = "start";
// 文字の垂直位置を設定
Ctx.textBaseline = "middle";
// 塗りつぶしの色を設定
Ctx.fillStyle = this.color;
// 塗りつぶしの文字を描画、(文字列, x軸, y軸, 最大幅)
Ctx.fillText(this.text,x,y,500);
}
}
// 楕円をインスタンス化
const Ellipse1 = new ellipse(50,80,0,2,'blue','skyblue');
const Ellipse2 = new ellipse(50,80,0,2,'red','pink');
// ラベルをインスタンス化
const Lavel1 = new lavel('定義域','black');
const Lavel2 = new lavel('終域','black');
// 楕円を描くメソッドを使う
Ellipse1.draw(mapping,60,100);
Ellipse2.draw(mapping,190,100);
// ラベルを描くメソッドを使う
Lavel1.draw(mapping,35,10);
Lavel2.draw(mapping,175,10);
</script>
ラベルを定義したコンストラクタ函数では、それがインスタンス化されるときにラベルの文字列とその文字の色を引数として受け取るようにした。
ラベルを定義したコンストラクタ函数の中で定義したdrawメソッドでは、それが呼び出される際にcanvas要素のIDとラベルが描かれる場所を示す座標を受け取るようにした。
要素オブジェクト
要素のオブジェクトを定義するコンストラクタ函数が次のコード。
<script>
// 楕円のコンストラクタ函数を省略
// ラベルのコンストラクタ函数を省略
// 要素のコンストラクタ函数を定義
function element(text,color){
// プロパティを定義
this.text = text; // 文字列
this.color = color; // 文字列の色
// 要素を描くメソッドを定義
this.draw = function(canvasId,x,y){
// 座標プロパティ
this.x = x; // 水平座標
this.y = y; // 垂直座標
// HTML5 Canvasを利用する
const Canvas = document.getElementById(canvasId);
const Ctx = Canvas.getContext("2d");
// フォントを設定
Ctx.font = 'bold 15px sans-serif';
// 文字の水平位置を設定
Ctx.textAlign = "start";
// 文字の垂直位置を設定
Ctx.textBaseline = "middle";
// 塗りつぶしの色を設定
Ctx.fillStyle = this.color;
// 塗りつぶしの文字を描画、(文字列,x軸,y軸,最大幅)
Ctx.fillText(this.text,x,y,500);
}
}
// 楕円をインスタンス化
const Ellipse1 = new ellipse(50,80,0,2,'blue','skyblue');
const Ellipse2 = new ellipse(50,80,0,2,'red','pink');
// ラベルをインスタンス化
const Lavel1 = new lavel('定義域','black');
const Lavel2 = new lavel('終域','black');
// 要素をインスタンス化
const Element1 = new element('a','black');
const Element2 = new element('b','black');
const Element3 = new element('d','black');
const Element4 = new element('e','black');
const Element5 = new element('f','black');
const Element6 = new eElement('α','black');
const Element7 = new element('β','black');
const Element8 = new element('γ','black');
const Element9 = new element('δ','black');
const Element10 = new element('ε','black');
// 楕円を描くメソッドを使う
Ellipse1.draw(mapping,60,100);
Ellipse2.draw(mapping,190,100);
// ラベルを描くメソッドを使う
Lavel1.draw(mapping,35,10);
Lavel2.draw(mapping,175,10);
// 要素を描くメソッドを使う
Element1.draw(mapping,55,40);
Element2.draw(mapping,55,70);
Element3.draw(mapping,55,100);
Element4.draw(mapping,55,130);
Element5.draw(mapping,55,160);
Element6.draw(mapping,185,40);
Element7.draw(mapping,185,70);
Element8.draw(mapping,185,100);
Element9.draw(mapping,185,130);
Element10.draw(mapping,185,160);
</script>
要素を定義したコンストラクタ函数では、それがインスタンス化されるときに文字列と文字列の色をその引数として受け取るようにした。
要素を定義したコンストラクタ函数の中で定義したdrawメソッドでは、それが呼び出される際にcanvas要素のIDと要素が描かれる場所を示す座標を受け取るようにした。
射オブジェクト
射のオブジェクトを定義するコンストラクタ函数が次のコード。
<script>
// 楕円のコンストラクタ函数を省略
// ラベルのコンストラクタ函数を省略
// 要素のコンストラクタ函数を省略
// 射のコンストラクタ函数を定義
function arrow(alength,width,color){
// プロパティを定義
this.length = alength; // 長さ
this.width = width; // 太さ
this.color = color; // 色
// 射を描くメソッドを定義
this.draw = function(canvasId,tailX,tailY,headX,headY){
// HTML5 Canvasを利用する
const Canvas = document.getElementById(canvasId);
const Ctx = Canvas.getContext("2d");
Ctx.beginPath(); // パスを初期化
Ctx.lineWidth = this.width; // 線の幅
Ctx.moveTo(tailX + 15, tailY); // 線の始点
Ctx.lineTo(headX + -5, headY); // 線の終点
Ctx.strokeStyle = this.color; // 線の色
Ctx.stroke(); // 線を描く
}
}
// 楕円をインスタンス化
const Ellipse1 = new ellipse(50,80,0,2,'blue','skyblue');
const Ellipse2 = new ellipse(50,80,0,2,'red','pink');
// ラベルをインスタンス化
const Lavel1 = new lavel('定義域','black');
const Lavel2 = new lavel('終域','black');
// 要素をインスタンス化
const Element1 = new element('a','black');
const Element2 = new element('b','black');
const Element3 = new element('d','black');
const Element4 = new element('e','black');
const Element5 = new element('f','black');
const Element6 = new element('α','black');
const Element7 = new element('β','black');
const Element8 = new element('γ','black');
const Element9 = new element('δ','black');
const Element10 = new element('ε','black');
// 射クラスのインスタンス化
const Arrow1 = new arrow(10,1,'black');
// 楕円を描くメソッドを使う
Ellipse1.draw(mapping,60,100);
Ellipse2.draw(mapping,190,100);
// ラベルを描くメソッドを使う
Lavel1.draw(mapping,35,10);
Lavel2.draw(mapping,175,10);
// 要素を描くメソッドを使う
Element1.draw(mapping,55,40);
Element2.draw(mapping,55,70);
Element3.draw(mapping,55,100);
Element4.draw(mapping,55,130);
Element5.draw(mapping,55,160);
Element6.draw(mapping,185,40);
Element7.draw(mapping,185,70);
Element8.draw(mapping,185,100);
Element9.draw(mapping,185,130);
Element10.draw(mapping,185,160);
// 射を描くメソッドを使う
Arrow1.draw(mapping,Element1.x,Element1.y,
Element6.x,Element6.y);
Arrow1.draw(mapping,Element2.x,Element2.y,
Element6.x,Element6.y);
Arrow1.draw(mapping,Element3.x,Element3.y,
Element7.x,Element7.y);
Arrow1.draw(mapping,Element4.x,Element4.y,
Element9.x,Element9.y);
Arrow1.draw(mapping,Element5.x,Element5.y,
Element10.x,Element10.y);
</script>
射を定義したコンストラクタ函数では、それがインスタンス化されるときに射の線の長さと太さと色を引数として受け取るようにした。
射を定義したコンストラクタ函数の中で定義したdrawメソッドでは、それが呼び出される際にcanvas要素のID、そして始点となる部分の水平座標と垂直座標、さらに終点となる部分の水平座標と垂直座標を受け取るようにした。
射の始点と終点の座標を指定するdrawメソッドの引数では要素オブジェクトのプロパティを利用している。
名前空間
最後に、変数名や函数名などが他のJavaSprictのコードと衝突しないように名前空間を作成した。JavaScriptでは正式に名前空間をサポートしていないけれども、函数や匿名函数やオブジェクト・リテラルを用いることでスコープを閉じることができるようなので、ここではそのために匿名函数を利用した。つまり、匿名函数でこれまで作成したコードをまるごと袋とじにした。
(function(){ // 匿名函数を使う
// ここにこれまで作成した一連のコードを置く。
})();
これは特に即時実行函数式とか即時呼び出し函数式と呼ばれることがある。
コメント
コメントを投稿