ソートアルゴリズムを可視化してみよう（２）

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

こちらのつづきです。

https://qiita.com/w0rc/items/cf77a44372fc7c3430ff

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

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

### DrawManager クラス

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

https://developer.mozilla.org/ja/docs/Web/API/Canvas_API/Tutorial/Basic_usage

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

```js: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` クラスでやっていることについて解説します。
メソッドはコンストラクタを含めて４つです。

#### constructor ( canvasId )
このクラスのコンストラクタです。
引数の `canvasId` から `<canvas>` 要素を取得し、
`<canvas>` 要素から「2 次元の描画コンテキスト」を取得します。
取得できない場合は例外をスローします。

また、`<canvas>` 要素の使い方で頭に入れておくべきこととしては、
APIの呼び出しは **命令型** のプログラミングが必要で、
**表示の重なりを意識して処理する** 必要があるということです。
とはいっても 2D グラフィックなのでルールは単純で、
「表示したいものを後に書けばよい」です。

#### drawData ( target )

引数の `target` は各ソートアルゴリズムから取り出して、
１フレームごとに呼び出します。
先述の通り、表示の重なりを意識しながら描画を進めていきます。

最初に「背景部分」を描画します。
`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` はその後の予定です。