Javascriptでライブラリを使わずに円グラフを描画する

こんばんは。きわさです。

javascriptで円グラフを描画したいときはChart.jsなどを使用するかと思いますが、今回はライブラリなしで描画したいと思います。

まずはcanvas要素を用意します。

<canvas id="canvas" width="200px" height="200px"></canvas>

次にjavascriptです。

function draw(data) {
    var canvas = document.getElementById("canvas");
    var context = canvas.getContext("2d");
    
    // 中心に移動
    context.translate(canvas.width / 2, canvas.height / 2);
    
    // 円グラフの半径
    var radius = canvas.width / 2;
    
    // グラフデータの色を適当に用意する
    var color = ["#f00", "#00f", "#ff0", "#0f0", "#0ff", "#f0f", "#f06", "#06f", "#6f0", "#0f6", "#60f", "#f60"];
    
    // データの合計値
    var total = data.reduce(function(p,c){return p+c;});
    
    // データの割合にあわせて描画する
    var startAngle = 0;
    for (var i = 0; i < data.length; i++) {
        var endAngle = 360 * data[i] / total + startAngle;
        buildPath(context, radius, startAngle, endAngle);
        context.fillStyle = color[i % color.length];
        context.fill();
        startAngle = endAngle;
    }
}

function buildPath(context, radius, startAngle, endAngle) {
    context.beginPath();
    context.arc(0, 0, radius, startAngle * Math.PI / 180, endAngle * Math.PI / 180, false);
    context.arc(0, 0, 0, endAngle * Math.PI / 180, startAngle * Math.PI / 180, true);
    context.closePath();
}

// 円グラフを描画
draw([11, 34, 20, 10, 10, 1, 16, 20, 5, 45, 8]);

下記のような円グラフとなります。

円を描く処理ですが、まずデータの合計値を求め、
各データの占める割合から角度を計算し、0度から360度に向かって円を描いていきます。
context.arc(x座標, y座標, 半径, 開始角, 終了角, 反時計回り)
で円を描きます。
この処理を開始角と終了角を入れ替えて2度行っているのは外側の円と内側の円を描いているためです。
内側の円の半径を0にしていますが、ここに値を設定すると下記のようなドーナツ型になります。

また、
context.isPointInPath(x, y)
という関数を使って座標のチェックを行うことで
クリックイベントなどを出しわけることができます。

function draw(data) {
    // 省略

    // クリックした時にデータを表示する
    canvas.onclick = function(event) {
        // 座標取得
        var x = event.clientX + window.pageXOffset - canvas.offsetLeft;
        var y = event.clientY + window.pageYOffset - canvas.offsetTop;
        
        // 円グラフのどのデータの位置にあるかチェックする
        var start = 0;
        for (var i = 0; i < data.length; i++) {
            var end = 360 * data[i] / total + start;
            buildPath(context, radius, start, end);
            if (context.isPointInPath(x, y)) {
                alert(data[i]);
                break;
            }
            start = end;
        }
    };
}

スポンサーリンク