JavaScript によるソートアルゴリズムの可視化
こちらのつづきです。
プログラムの解説 - メイン処理 その他
前回、main.js
で解説していなかった DrawManager
についての解説です。
DrawManager クラス
このプログラムで <canvas>
要素への描画を担うクラスです。
そもそも <canvas>
って何だ?が気になる方はこちらもご参照ください。
それでは DrawManager クラスの内容です。
/**
* 描画管理クラス
*/
class DrawManager {
canvas;
context;
before = { target: undefined, strings: [] };
/**
* 描画管理クラスのコンストラクタ
* @param {string} canvasId canvas 要素の ID
*/
constructor ( canvasId ) {
// キャンバスの取得
this.canvas = document.getElementById( canvasId );
if ( !this.canvas || !this.canvas.getContext ) {
throw "canvas is undefined";
}
// コンテキストの取得
this.context = this.canvas.getContext( "2d" );
if ( !this.context ) {
throw "context is undefined";
}
}
/**
* 対象データを描画する
* @param {Object} target 描画データのオブジェクト.
*/
drawData ( target ) {
// 背景を塗りつぶす
this.context.fillStyle = "#336666";
this.context.fillRect( 0, 0, this.canvas.width, this.canvas.height );
// データをヒストグラムとして描画する
if ( target ) {
this.before.target = target;
const data = target.data;
const coloringIndices = target.coloringIndices;
// ヒストグラムの幅と高さを調整
const dw = this.canvas.width / data.length;
const dh = this.canvas.height / ( data.length + 1 );
const margin = dw * 0.1;
// ヒストグラムのカラーを調整
const max = data.reduce( ( a,b ) => a > b ? a : b );
const base = 51;
const gradient = ( 256 - base ) / max;
// ヒストグラムを描画する
data.forEach( ( e, i ) => {
this.context.fillStyle = `rgb(${gradient * e + base}, ${gradient * e + base}, ${gradient * e + base})`;
if ( coloringIndices ) {
if ( i === coloringIndices[0] ) this.context.fillStyle = "#993333";
if ( i === coloringIndices[1] ) this.context.fillStyle = "#339933";
if ( i === coloringIndices[2] ) this.context.fillStyle = "#333399";
}
this.context.fillRect( i * dw + margin, this.canvas.height - ( e * dh ), dw - margin, e * dh );
} );
}
}
/**
* テキストを描画する
* @param {...Object} args 描画する文字のオブジェクト. { string:文字列, line:表示行数 }
*/
drawString ( ...args ) {
this.before.strings = args;
this.context.font = "14px serif";
this.context.fillStyle = "#ffffff";
args.forEach( ( value ) => {
this.context.fillText( value.str, 10, 15 * value.line );
} );
}
/**
* 前回データのまま再描画する
*/
redraw ( ) {
this.drawData ( this.before.target );
this.drawString( ...this.before.strings );
}
}
この DrawManager
クラスでやっていることについて解説します。
メソッドはコンストラクタを含めて4つです。
constructor ( canvasId )
このクラスのコンストラクタです。
引数の canvasId
から <canvas>
要素を取得し、
<canvas>
要素から「2 次元の描画コンテキスト」を取得します。
取得できない場合は例外をスローします。
また、<canvas>
要素の使い方で頭に入れておくべきこととしては、
APIの呼び出しは 命令型 のプログラミングが必要で、
表示の重なりを意識して処理する 必要があるということです。
とはいっても 2D グラフィックなのでルールは単純で、
「表示したいものを後に書けばよい」です。
drawData ( target )
引数の target
は各ソートアルゴリズムから取り出して、
1フレームごとに呼び出します。
先述の通り、表示の重なりを意識しながら描画を進めていきます。
最初に「背景部分」を描画します。
context.fillStyle
で色を決めて、
context.fillRect
で塗りつぶします。
通常、キャンバスのクリアには clearRect()
メソッドを呼びますが、
今回は背景色での全面塗りつぶしで十分です。
続いて「ヒストグラム部分」を描画していきますが、
その前にサイズや色の調整処理を挟んでいます。
幅と高さの調整はヒストグラムとして見せるために必要ですが、
色はお好みなので適当です。
ちなみに "#333333"~"#ffffff" のグラデーションにしています。
そして、各データを forEach で取り出しながら描画します。
背景と同様に context.fillStyle
で描画する色を設定しますが、
この時に引数の coloringIndices
も見ています。
ソートアルゴリズムで処理中のインデックスを色分けすることで、
動いている感じを見やすくするための工夫です。
そして context.fillRect
で調整した幅と高さで描画します。
drawString ( ...args )
引数として文字列と行番号をもつオブジェクトを渡して、
画面に文字列を描画するためのメソッドです。
文字列を描画するときは呼び出す API がちょっと変わります。
フォント関連の情報が必要なので、
context.font
を設定しています。
context.fillStyle
は色の設定なので先ほどと同様、
context.fillText
で文字列の描画をします。
この drawString()
メソッドは必ず drawData()
よりも後に
呼び出す必要があります。
( drawData()
側で背景塗りつぶしをしているため )
思いつきで付け足したせいでアンチパターンの例みたいな作りですね
redraw ( )
ウインドウリサイズのイベントリスナーから呼ぶ目的の関数です。
引数として this.before.xxx
を渡すかたちで、
drawData()
と drawString()
を呼び出します。
各描画メソッドに「最後に渡されたパラメータ」を保持させ、
「前フレームと同じ描画をやり直す」を実現しています。
アニメーション停止中にリサイズが発生すると、
キャンバスが見えなくなってしまう状態の対処となっています。
main.js
は以上になります。
次回 util.js
で、残る algorithms.js
はその後の予定です。