ブログの説明

学校に通わないで学んだことを記しています。間違っているところが何かありましたらご指摘下さると幸いです。コメントに対する返信が遅れる可能性があります。その場合は申し訳ありません。

このブログではサイドバーに広告を表示しています。このブログ内の投稿記事を検索するには右上の拡大鏡のアイコンを、アーカイブやラベル付けから投稿記事を閲覧するには左上の三重線のアイコンをクリックして下さい。

数式の表示にはMathJaxを利用させていただいています。数式の表示のためにJavaScriptが有効である必要があります。そうでない場合、訳の分からないLatexのコードが表示されます。幾何学図形やチャートの表示にはHTML5 CanvasやGoogle Chartを使用しています。その表示のためにもJavaScriptが有効である必要があります。

JavaScriptとHTML5 Canvas 2Dを使ってベン図を描く

HTML5 Canvas 2Dを使ってベン図(ヴェン図)をHTML文書上に描くための方法に関する覚え書き。次のような、ちょっとしたJavaScriptアプリを作る手順を示す。

ベン図のJavaScriptアプリ このブラウザはHTML5 Canvasに対応していません。

HTML5 Canvas 2Dを使うための準備

HTML5 CanvasをHTML文書に埋め込むにはcanvas要素を使う。

<canvas id='canvas_name' width='250' height='180'>
あなたのブラウザはCanvas要素に対応していません。
</canvas>

canvasタグにidと幅(width)と高さ(height)という三つの属性を指定し、canvasに対応していないウェブ・ブラウザ向けのメッセージ(例えばCanvas not supported)をその間に入れてcanvas要素の終了タグではさむ。 canvasタグに挟まれた間の文字列は、Canvasに対応しているウェブ・ブラウザでは表示されない。ちなみに、JavaScriptを無効にしてあるとCanvasも無効になる。

idはウェブ・ページに表示するいくつかのcanvas要素を識別するためのもの。同じウェブ・ページにいくつかのcanvasを埋め込むためにはこのidがそれぞれに異なっている必要がある。

幅と高さはCanvasの四角形の枠の大きさを表わす。この幅と高さの枠の中に図が描かれる。この枠をはみ出すと表示されない。大きくしすぎると余白ができてしまうし、小さくしすぎると図がはみ出して見えなくなってしまう。幅と高さ以外の細かい設定は、canvas要素かid属性またはclass属性をセレクタに指定してスタイルシートを使って行うことができる。

次のスタイルシートの記述は、canvasセレクタでCanvasの外枠の線の太さを1pxに、線の種類を一本の実線に、その色を青に設定し、canvas_nameセレクタで背景色を白に設定している。

canvas {
  border:1px solid 'blue';
}
#canvas_name {
  background-color: 'white';
}

ただし、スタイルシートではCanvas APIによる描画内容まで制御することはできない。

フォームの作成

次にHTMLのform要素を用意する。formタグの間にはinput要素を並べてラジオボタンを作成する。input要素はlabelタグではさんでラベル名と関連付ける。さらにsubmitボタンも用意する。

form要素にはaction属性とonsubmit属性が必要になる。

<form name="sets" action="javascript:void(0)" onsubmit="event_handle();">
<label><input type="radio" name="set" value="wa">和集合</label>
<label><input type="radio" name="set" value="seki">共通部分</label>
<label><input type="radio" name="set" value="sa">差集合</label>
<label><input type="radio" name="set" value="ho">補集合</label>
<label><input type="radio" name="set" value="tai">対称差</label>
<label><input type="radio" name="set" value="zen">全体集合U</label>
<input type="submit" value="決定">
</form>

onsubmit="event_handle();"は、form要素の中にあるinput要素のひとつ、submitボタンが押されたときに反応する処理(JavaScriptの函数名)を記述してある。

action="javascript:void(0)"は、これが指定されていないとsubmitボタンを押したときにページが再読み込みされてしまい、実行結果が一瞬で消えてしまって結果が反映されない。しかしこれがなくてもonsubmit="return event_handle();"と指定して函数event_handle内の末尾にretuen false;と記述することで同じ機能を代替することができる。

input要素でtype属性にradioを指定したラジオボタンは、それらのname属性を同じにすることで排他的な選択肢として機能させることができるようになる。

JavaScriptのコーディング

JavaScriptのコードは、script要素、すなわちscriptタグにはさまれた中に次のように入力する。ちなみに、script要素はcanvas要素よりも後に配置しないと認識されないようだ。

<script>
...ここにJavaScriptのコードを記述
</script>

ここで作るJavaScriptアプリのソースコードは、大きく分けてふたつの函数から成り立っている。そのひとつはform要素に関連付けられた処理をするための函数。もうひとつはCanvas 2Dのコードを記述した函数。まず後者の函数にcanvasという名前を与えてそちらからコーディングしよう。函数canvasはふたつの引数を受け取る。ここではそのふたつの引数に、prop1, prop2という変数名をそれぞれ与えた。

<script>
function canvas(prop1, prop2) {
  ここにこの函数のコードを記述...
}
</script>

JavaScriptからHTMLのcanvas要素への参照を得るためにはDOM(Document Object Model)と呼ばれるAPIの一種を利用する。

<script>
function canvas(prop1, prop2) {
  var the_canvas = document.getElementById("canvas_name");
  var ctx = the_canvas.getContext("2d");
  ....ここにCanvas APIのコードを入力....
}
</script>

var the_canvas = document.getElementById("canvas_name");では、documentオブジェクトのgetElementByIdメソッドを使ってcanvas_nameというidを持つcanvas要素への参照を得て、それをthe_canvasという変数に代入している。var ctx = the_canvas.getContext("2d");では、CanvasオブジェクトのgetContextメソッドを使ってCanvasオブジェクトの2Dコンテキストへの参照を得て、それをctxという変数に代入している。このふたつのコードはvar ctx = document.getElementById("canvas_name").getContext("2d");というひとつのコードにまとめてもよい。

これでJavaScriptからHTML5 Canvas 2DのAPIを利用する準備ができた。以降は、ctxというオブジェクト変数を利用して図形を描く。

ベン図(ヴェン図)を描く

ここで描くベン図は主として三つの層を持っていると考えることができるかもしれない。第一は塗りつぶしの層、第二は線分の層、第三は文字の層。この順番は描画の順番を意味している。ただし、コーディングは第二の線分の層からやったほうが都合がよい。そのため、まずは次のような線分を描くことから始める。始点から終点までの6つの位置を直線で結ぶ。

このブラウザはHTML5 Canvasに対応していません。

Canvas内の位置関係は、左上端を(0,0)とする座標系(x,y)によって表わす。x座標は右への距離を意味し、y座標は下への距離を意味する。

<script>
function canvas(prop1, prop2) {
  var the_canvas = document.getElementById("canvas_name");
  var ctx = the_canvas.getContext("2d");
  
  // 線分の層

  // 全体集合の線分
  ctx.beginPath();  // パスの開始
  ctx.moveTo(20, 20);  // 始点
  ctx.lineTo(5, 20);  // 線分1
  ctx.lineTo(5, 170);  // 線分2
  ctx.lineTo(230, 170);  // 線分3
  ctx.lineTo(230, 20);  // 線分4
  ctx.lineTo(45, 20);  // 終点
  ctx.closePath();  // パスを閉じる
  ctx.strokeStyle = 'black';  // 線の色
  ctx.lineWidth = 2;  // 線の太さ
  ctx.stroke();  // 線を描く
}
</script>

まず、線分を描く通り道(パス)をbeginPathメソッドを使って初期化する。次に、線分を描き始める座標の位置をmoveToメソッドで指定し、これを始点(20,20)とする。そこから線をどの位置まで引くかを次のlineToメソッドで指定しつづけ、終点の位置(45,20)で止める。

パスの指定がすべて終わったらclosePath()メソッドでパスを閉じる。

そして、線分のスタイルを決めるstrokeStyleプロパティに線分の色名を代入し、線分の太さを決めるlineWidthプロパティに数値を代入する。こうしておいてstrokeメソッドで線分を描く。

これで全体集合を表わす四角い線分を描くコードが完成。四角いと言っても、ここでは線分を完全に閉じずに隙間を空けている。ここに文字を置くため。

次に、集合を表わすふたつの円弧を一部が重なるように描く。円弧は閉じず、文字を描く部分だけ隙間を空けておく。

このブラウザはHTML5 Canvasに対応していません。

このソースコードは次のとおり。

<script>
function canvas(prop1, prop2) {
  var the_canvas = document.getElementById("sets_sen");
  var ctx = the_canvas.getContext("2d");

  // 線分の層

  // 全体集合の線分
  ctx.beginPath();  // パスの開始
  ctx.moveTo(20, 20);  // 始点
  ctx.lineTo(5, 20);  // 線分1
  ctx.lineTo(5, 170);  // 線分2
  ctx.lineTo(230, 170);  // 線分3
  ctx.lineTo(230, 20);  // 線分4
  ctx.lineTo(45, 20);  // 終点
  ctx.closePath();  // パスを閉じる
  ctx.strokeStyle = 'black';  // 線の色
  ctx.lineWidth = 2;  // 線の太さ
  ctx.stroke();  // 線を描く

  // 集合A(円弧)
  ctx.beginPath();  // パスを開始
  ctx.arc(80, 100, 50, -1.3, 4.45, false); // 円弧
  ctx.closePath();  // パスを閉じる
  ctx.lineWidth = 2;  // 線の太さ
  ctx.strokeStyle = 'black';  // 線の色
  ctx.stroke(); // 線を描く

  // 集合B(円弧)
  ctx.beginPath();
  ctx.arc(150, 100, 50, -1.3, 4.45, false);
  ctx.closePath();
  ctx.lineWidth = 2;
  ctx.strokeStyle = 'black';
  ctx.stroke();
}
</script>

まず、線を描く通り道(パス)をbeginPathメソッドを使って初期化する。次にarcメソッドを使って円を描く位置や円の大きさなどを決める。arcメソッドの引数の書式は次のよう:arc(x座標,y座標,半径の長さ,開始角度,終了角度,描く向き);

arcメソッドで指定する座標上の位置は円弧の中心の位置を指す。開始角度と終了角度は度数法ではなく弧度法(ラジアン)で指定する必要がある。弧度法では、12進数時計でいうところの3時15分のところから始まり、反時計回りに回って3.15あたりで半周、6.3あたりで一周する。このソースコードではラジアンで指定してあるが、度数法に変換したほうが直観的で分かりやすい。度数法によって指定するには、弧度法から度数法への変換公式を使って(Math.PI/180)*-75, (Math.PI/180)*255, falseとする。

arcメソッドの最後の引数は描く向きを示す。時計回りなら1かtrueを指定し、反時計回りなら0かfalseを指定する。ここでは弧度法において正の向きとされる反時計回りに指定するためにfalseとした。ここをtrueにした場合には開始角度の値と終了角度の値を置き換える必要がある。

パスの指定が終わったらclosePath()メソッドでパスを閉じる。

lineWidthプロパティに線の太さを代入し、strokeStyleプロパティに線の色を代入し、strokeメソッドで線を描けば完了。

これで線の層は完成した。次は塗りつぶしの層にとりかかる。塗りつぶしの層の作業は簡単。線の層のコードをコピーして線の層のコードよりも前に貼り付ける。そして、lineWidthプロパティとstrokeStyleプロパティとstrokeメソッドのコードを削除し、それらをfillStyleプロパティとfillメソッドに置き換える。

filleStyleプロパティには塗りつぶす色を代入する。ここではyellow(黄色)にした。

塗りつぶしの層でもっとも重要なのは塗りつぶしの色が重なる部分の処理。この処理によって和集合や共通部分や差集合などを塗りつぶしの色によって表現することができるようになる。そのためにはglobalCompositeOperationプロパティを使う。このプロパティのコードを塗りつぶす三つの図形のコードの間にふたつ置く。そして函数canvasの引数であるprop1とprop2をそれぞれに代入する。最後にもうひとつ、塗りつぶし層と線分の層との間にもglobalCompositeOperationプロパティを配置し、そこに'source-over'というデフォルト値を代入する。

そうすると、函数canvasの内容は次のようになるはず。

<script>
function canvas(prop1, prop2) {
  var the_canvas = document.getElementById("sets_sen");
  var ctx = the_canvas.getContext("2d");

  // 塗りつぶしの層

  // 全体集合の線分
  ctx.beginPath();  // パスの開始
  ctx.moveTo(20, 20);  // 始点
  ctx.lineTo(5, 20);  // 線分1
  ctx.lineTo(5, 170);  // 線分2
  ctx.lineTo(230, 170);  // 線分3
  ctx.lineTo(230, 20);  // 線分4
  ctx.lineTo(45, 20);  // 終点
  ctx.closePath();  // パスを閉じる
  ctx.fillStyle = 'yellow';  // 塗りつぶす色
  ctx.fill();  // 塗りつぶす

  // 塗りつぶしの重なる部分の描画方法
  ctx.globalCompositeOperation = prop1;

  // 集合A(円弧)
  ctx.beginPath();  // パスを開始
  ctx.arc(80, 100, 50, -1.3, 4.45, false); // 円弧
  ctx.closePath();  // パスを閉じる
  ctx.fillStyle = 'yellow';  // 塗りつぶす色
  ctx.fill(); // 塗りつぶす

  // 塗りつぶしの重なる部分の描画方法
  ctx.globalCompositeOperation = prop2;
  
  // 集合B(円弧)
  ctx.beginPath();
  ctx.arc(150, 100, 50, -1.3, 4.45, false);
  ctx.closePath();
  ctx.fillStyle = 'yellow';
  ctx.fill();

  // デフォルト値に戻す
  ctx.globalCompositeOperation = 'source-over';

  // 線分の層

  // 全体集合の線分
  ctx.beginPath();  // パスの開始
  ctx.moveTo(20, 20);  // 始点
  ctx.lineTo(5, 20);  // 線分1
  ctx.lineTo(5, 170);  // 線分2
  ctx.lineTo(230, 170);  // 線分3
  ctx.lineTo(230, 20);  // 線分4
  ctx.lineTo(45, 20);  // 終点
  ctx.closePath();  // パスを閉じる
  ctx.strokeStyle = 'black';  // 線の色
  ctx.lineWidth = 2;  // 線の太さ
  ctx.stroke();  // 線を描く

  // 集合A(円弧)
  ctx.beginPath();  // パスを開始
  ctx.arc(80, 100, 50, -1.3, 4.45, false); // 円弧
  ctx.closePath();  // パスを閉じる
  ctx.lineWidth = 2;  // 線の太さ
  ctx.strokeStyle = 'black';  // 線の色
  ctx.stroke(); // 線を描く
  
  // 集合B(円弧)
  ctx.beginPath();
  ctx.arc(150, 100, 50, -1.3, 4.45, false);
  ctx.closePath();
  ctx.lineWidth = 2;
  ctx.strokeStyle = 'black';
  ctx.stroke();
}
</script>

次に、文字の層を函数canvas内のいちばん末尾にコーディングする。文字とはベン図の三つの集合(全体集合U、集合A、集合B)に書き込むU, A, Bという文字のこと。文字の描画は次のようにする。

<script>
function canvas(prop1, prop2) {
  ...中略...

  // 文字の層

  // フォント
  ctx.font = 'oblique bold 30px "MS P明朝", sans-serif';
  // 塗りつぶしの色
  ctx.fillStyle = 'green';
  // 文字の水平位置
  ctx.textAlign = 'start';
  // 文字の垂直位置
  ctx.textBaseline = 'middle';
  // 文字を描画、(文字列, x軸, y軸, 最大幅)
  ctx.fillText('U', 22, 20, 500);

  ctx.font = 'oblique bold 30px "MS P明朝", sans-serif';
  ctx.fillStyle = 'blue';
  ctx.textAlign = 'start';
  ctx.textBaseline = 'middle';
  ctx.fillText('A', 70, 50, 500);

  ctx.font = 'oblique bold 30px "MS P明朝", sans-serif';
  ctx.fillStyle = 'red';
  ctx.textAlign = 'start';
  ctx.textBaseline = 'middle';
  ctx.fillText('B', 140, 50, 500);
}
</script>

文字の層では、fontプロパティにおいて、文字フォントのスタイル、太さ、大きさ、種類を指定し、カンマを入れてsans-serifを指定し、これらを代入する。すべてが必須というわけではない。

fillStyleプロパティには文字を塗りつぶす色を代入する。textAlignプロパティでは、文字を描画する座標系の位置を文字の横方向のどのあたりを規準にするかを決める。代入する値は、startまたはleft(語頭), endまたはright(語末), center(中央)のいずれかから選ぶことができる。

textBaselineプロパティでは、文字を描画する座標系の位置を文字の縦方向のどのあたりを規準にするかを決める。代入する値は、topまたはhanging、middle、alphabetic、ideographicまたはbottomのいずれかから選ぶことができる。

そして最後に文字列を描画するためのfillTextメソッドを使って文字を描画する。fillTextメソッドはfillText('文字列', x座標, y座標, 最大幅)という書式になる。

文字の層を函数canvas内の末尾に加えると、函数canvasは最終的に次のようになる。

<script>
function canvas(prop1, prop2) {
  var the_canvas = document.getElementById("sets_sen");
  var ctx = the_canvas.getContext("2d");

  // 塗りつぶしの層

  // 全体集合の線分
  ctx.beginPath();  // パスの開始
  ctx.moveTo(20, 20);  // 始点
  ctx.lineTo(5, 20);  // 線分1
  ctx.lineTo(5, 170);  // 線分2
  ctx.lineTo(230, 170);  // 線分3
  ctx.lineTo(230, 20);  // 線分4
  ctx.lineTo(45, 20);  // 終点
  ctx.closePath();  // パスを閉じる
  ctx.fillStyle = 'yellow';  // 塗りつぶす色
  ctx.fill();  // 塗りつぶす

  // 塗りつぶしの重なる部分の描画方法
  ctx.globalCompositeOperation = prop1;

  // 集合A(円弧)
  ctx.beginPath();  // パスを開始
  ctx.arc(80, 100, 50, -1.3, 4.45, false); // 円弧
  ctx.closePath();  // パスを閉じる
  ctx.fillStyle = 'yellow';  // 塗りつぶす色
  ctx.fill(); // 塗りつぶす

  // 塗りつぶしの重なる部分の描画方法
  ctx.globalCompositeOperation = prop2;
  
  // 集合B(円弧)
  ctx.beginPath();
  ctx.arc(150, 100, 50, -1.3, 4.45, false);
  ctx.closePath();
  ctx.fillStyle = 'yellow';
  ctx.fill();

  // デフォルト値に戻す
  ctx.globalCompositeOperation = 'source-over';

  // 線分の層

  // 全体集合の線分
  ctx.beginPath();  // パスの開始
  ctx.moveTo(20, 20);  // 始点
  ctx.lineTo(5, 20);  // 線分1
  ctx.lineTo(5, 170);  // 線分2
  ctx.lineTo(230, 170);  // 線分3
  ctx.lineTo(230, 20);  // 線分4
  ctx.lineTo(45, 20);  // 終点
  ctx.closePath();  // パスを閉じる
  ctx.strokeStyle = 'black';  // 線の色
  ctx.lineWidth = 2;  // 線の太さ
  ctx.stroke();  // 線を描く

  // 集合A(円弧)
  ctx.beginPath();  // パスを開始
  ctx.arc(80, 100, 50, -1.3, 4.45, false); // 円弧
  ctx.closePath();  // パスを閉じる
  ctx.lineWidth = 2;  // 線の太さ
  ctx.strokeStyle = 'black';  // 線の色
  ctx.stroke(); // 線を描く
  
  // 集合B(円弧)
  ctx.beginPath();
  ctx.arc(150, 100, 50, -1.3, 4.45, false);
  ctx.closePath();
  ctx.lineWidth = 2;
  ctx.strokeStyle = 'black';
  ctx.stroke();

  // 文字の層

  // フォント
  ctx.font = 'oblique bold 30px "MS P明朝", sans-serif';
  // 塗りつぶしの色
  ctx.fillStyle = 'green';
  // 文字の水平位置
  ctx.textAlign = 'start';
  // 文字の垂直位置
  ctx.textBaseline = 'middle';
  // 文字を描画、(文字列, x軸, y軸, 最大幅)
  ctx.fillText('U', 22, 20, 500);

  ctx.font = 'oblique bold 30px "MS P明朝", sans-serif';
  ctx.fillStyle = 'blue';
  ctx.textAlign = 'start';
  ctx.textBaseline = 'middle';
  ctx.fillText('A', 70, 50, 500);

  ctx.font = 'oblique bold 30px "MS P明朝", sans-serif';
  ctx.fillStyle = 'red';
  ctx.textAlign = 'start';
  ctx.textBaseline = 'middle';
  ctx.fillText('B', 140, 50, 500);
}
</script>

これで函数canvasが完成した。次にとりかかるのは、form要素から受け取ったonsubmitのイベントを処理するコーディング。この函数をここではevent_handleと名づけた。

函数event_handleでは、form要素内のsubmitボタンが押されたときに発生するonsubmitイベントを感知し、どのラジオボタンが選択されているかをチェックし、選択されているラジオボタンのvalue属性の値を受け取る必要がある。そのためにはdocumentオブジェクトのquerySelectorメソッドを使う。

querySelectorメソッドの引数をquerySelector('input[name="set"]:checked')というふうに指定すると、setという名前を持つinput要素のうちで選択されている要素を取得することができる。そうしてそのvalueプロパティの値(ここでは'wa', 'seki', 'sa', 'ho'など)を変数xに代入したのが次のコード。

<script>
function event_handle() {
  var x = document.querySelector('input[name="set"]:checked').value;
}
</script>

次に、変数xに代入された値によって処理を分岐させるためにswitch文を使う。そうすると、次のようなコードが出来上がる。

<script>
function event_handle() {
  var x = document.querySelector('input[name="set"]:checked').value;

  var prop1;
  var prop2;

  switch(x){
    case 'wa':  // 和集合
      prop1 = 'source-in';
      prop2 = 'source-over';
      break;
    case 'seki':  // 共通部分
      prop1 = 'source-in';
      prop2 = 'source-in';
      break;
    case 'sa':  // 差集合
      prop1 = 'source-in';
      prop2 = 'destination-out';
      break;
    case 'ho':  // 補集合
      prop1 = 'destination-out';
      prop2 = 'source-atop';
      break;
    case 'tai': // 対称差
      prop1 = 'source-in';
      prop2 = 'xor';
      break;
    case 'zen': // 全体集合
      prop1 = 'source-over';
      prop2 = 'source-over';
      break;
  }
}
</script>

変数prop1と変数prop2は、函数canvas内にコーディングしておいたglobalCompositeOperationプロパティに代入するためのもの。globalCompositeOperationプロパティには次のような値を代入することができる。

source-over(既定値)
先に描画したものが背後に隠れ、後に描画したものによって上書きされる。
source-atop
先に描画したものが背後に隠れ、後に描画したものは先に描画したものと重なる部分だけが描画される。
source-in
先に描画したものが消え、後に描画したものは先に描画したものと重なる部分だけが描画される。
source-out
先に描画したものが消え、先に描画したものと後に描画したものとが重なる部分も消えて残った部分が描画される。
destination-over
後に描画したものが背後に隠れ、先に描画したものによって上書きされる。
destination-atop
後に描画したものが背後に隠れ、先に描画したものは後に描画したものと重なる部分だけが描画される。
destination-in
後に描画したものが消え、先に描画したものは後に描画したものと重なる部分だけが描画される。
destination-out
後に描画したものが消え、先に描画したものは後に描画したものと重なる部分が消えて残った部分だけが描画される。
copy
後に描画されたものだけが描画され、先に描画されたものは無視される。
lighter
先に描画されたものと後に描画されたものが重なる部分が明るく描画される。
xor
先に描画されたものと後に描画されたものが重なる部分だけが消える。

ここで作成するベン図は三つの層が重なっているので少し複雑になる。頭がこんがらがってしまうので、globalCompositeOperationプロパティに代入するこれらの値をいろいろと入れ替えてみて実験してみるのが現実的。

これらの値を代入した変数prop1と変数prop2を引数にして函数canvasを呼び出すコードを最後に加えれば、函数event_handleは出来上がり。

<script>
function event_handle() {
  var x = document.querySelector('input[name="set"]:checked').value;

  switch(x){
    case 'wa':  // 和集合
      var prop1 = 'source-in';
      var prop2 = 'source-over';
      break;
    case 'seki':  // 共通部分
      var prop1 = 'source-in';
      var prop2 = 'source-in';
      break;
    case 'sa':  // 差集合
      var prop1 = 'source-in';
      var prop2 = 'destination-out';
      break;
    case 'ho':  // 補集合
      var prop1 = 'destination-out';
      var prop2 = 'source-atop';
      break;
    case 'tai': // 対称差
      var prop1 = 'source-in';
      var prop2 = 'xor';
      break;
    case 'zen': // 全体集合
      var prop1 = 'source-over';
      var prop2 = 'source-over';
      break;
  }
  canvas(prop1, prop2);
}
</script>

函数canvasは、このJavaScriptのコードが最初にウェブ・ブラウザに読み込まれたときにも実行されるように、函数の外にも置く必要がある。その場合の引数はcanvas('source-out', 'source-in');のようになる。これはいずれの集合もまったく塗りつぶさない初期状態を意味している。

これらのコードをすべてまとめると最終的に次のようなソースコードが出来上がるはず。

<script>
// Canvasにベン図を描画する函数
function canvas(prop1, prop2) {
  var the_canvas = document.getElementById("sets_sen");
  var ctx = the_canvas.getContext("2d");

  // 塗りつぶしの層

  // 全体集合の線分
  ctx.beginPath();  // パスの開始
  ctx.moveTo(20, 20);  // 始点
  ctx.lineTo(5, 20);  // 線分1
  ctx.lineTo(5, 170);  // 線分2
  ctx.lineTo(230, 170);  // 線分3
  ctx.lineTo(230, 20);  // 線分4
  ctx.lineTo(45, 20);  // 終点
  ctx.closePath();  // パスを閉じる
  ctx.fillStyle = 'yellow';  // 塗りつぶす色
  ctx.fill();  // 塗りつぶす

  // 塗りつぶしの重なる部分の描画方法
  ctx.globalCompositeOperation = prop1;

  // 集合A(円弧)
  ctx.beginPath();  // パスを開始
  ctx.arc(80, 100, 50, -1.3, 4.45, false); // 円弧
  ctx.closePath();  // パスを閉じる
  ctx.fillStyle = 'yellow';  // 塗りつぶす色
  ctx.fill(); // 塗りつぶす

  // 塗りつぶしの重なる部分の描画方法
  ctx.globalCompositeOperation = prop2;
  
  // 集合B(円弧)
  ctx.beginPath();
  ctx.arc(150, 100, 50, -1.3, 4.45, false);
  ctx.closePath();
  ctx.fillStyle = 'yellow';
  ctx.fill();

  // デフォルト値に戻す
  ctx.globalCompositeOperation = 'source-over';

  // 線分の層

  // 全体集合の線分
  ctx.beginPath();  // パスの開始
  ctx.moveTo(20, 20);  // 始点
  ctx.lineTo(5, 20);  // 線分1
  ctx.lineTo(5, 170);  // 線分2
  ctx.lineTo(230, 170);  // 線分3
  ctx.lineTo(230, 20);  // 線分4
  ctx.lineTo(45, 20);  // 終点
  ctx.closePath();  // パスを閉じる
  ctx.strokeStyle = 'black';  // 線の色
  ctx.lineWidth = 2;  // 線の太さ
  ctx.stroke();  // 線を描く

  // 集合A(円弧)
  ctx.beginPath();  // パスを開始
  ctx.arc(80, 100, 50, -1.3, 4.45, false); // 円弧
  ctx.closePath();  // パスを閉じる
  ctx.lineWidth = 2;  // 線の太さ
  ctx.strokeStyle = 'black';  // 線の色
  ctx.stroke(); // 線を描く
  
  // 集合B(円弧)
  ctx.beginPath();
  ctx.arc(150, 100, 50, -1.3, 4.45, false);
  ctx.closePath();
  ctx.lineWidth = 2;
  ctx.strokeStyle = 'black';
  ctx.stroke();

  // 文字の層

  // フォント
  ctx.font = 'oblique bold 30px "MS P明朝", sans-serif';
  // 塗りつぶしの色
  ctx.fillStyle = 'green';
  // 文字の水平位置
  ctx.textAlign = 'start';
  // 文字の垂直位置
  ctx.textBaseline = 'middle';
  // 文字を描画、(文字列, x軸, y軸, 最大幅)
  ctx.fillText('U', 22, 20, 500);

  ctx.font = 'oblique bold 30px "MS P明朝", sans-serif';
  ctx.fillStyle = 'blue';
  ctx.textAlign = 'start';
  ctx.textBaseline = 'middle';
  ctx.fillText('A', 70, 50, 500);

  ctx.font = 'oblique bold 30px "MS P明朝", sans-serif';
  ctx.fillStyle = 'red';
  ctx.textAlign = 'start';
  ctx.textBaseline = 'middle';
  ctx.fillText('B', 140, 50, 500);
}

// form要素からのイベントに応える函数
function event_handle() {
  var x = document.querySelector('input[name="set"]:checked').value;

  switch(x){
    case 'wa':  // 和集合
      var prop1 = 'source-in';
      var prop2 = 'source-over';
      break;
    case 'seki':  // 共通部分
      var prop1 = 'source-in';
      var prop2 = 'source-in';
      break;
    case 'sa':  // 差集合
      var prop1 = 'source-in';
      var prop2 = 'destination-out';
      break;
    case 'ho':  // 補集合
      var prop1 = 'destination-out';
      var prop2 = 'source-atop';
      break;
    case 'tai': // 対称差
      var prop1 = 'source-in';
      var prop2 = 'xor';
      break;
    case 'zen': // 全体集合
      var prop1 = 'source-over';
      var prop2 = 'source-over';
      break;
  }
  // Canvasにベン図を描画する函数を呼び出す
  canvas(prop1, prop2);
}


// 最初にべん図を描画するために函数canvasを呼び出す
canvas('source-out', 'source-in');
</script>

このソースコードをscript要素として、HTML文書の中でcanvas要素よりも後に配置しておけば、このベン図アプリが機能するはず。

コメント

このブログの人気の投稿

Visual Studio 2019にはC++のためのフォームデザイナーがない件

10の補数と9の補数と2の補数と1の補数

LATEXで数式:指数と順列などで使う添数・添字