0
1

ソートアルゴリズムを可視化してみよう(2)

Posted at

JavaScript によるソートアルゴリズムの可視化

こちらのつづきです。

プログラムの解説 - メイン処理 その他

前回、main.js で解説していなかった DrawManager についての解説です。

DrawManager クラス

このプログラムで <canvas> 要素への描画を担うクラスです。
そもそも <canvas> って何だ?が気になる方はこちらもご参照ください。

それでは DrawManager クラスの内容です。

main.js より抜粋
/**
 *  描画管理クラス
 */
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 はその後の予定です。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1