JavaScriptには多くのライブラリがあり、日々の業務でもお世話になっています。
表題にもある円グラフをはじめグラフ表示となるとChart.jsを個人的にはよく使っているのですが、それだと表示要件をクリアできないものに出くわしました(Chart.jsでも拡張すれば再現できるのかもしれませんが、、)。
要件的には
といったものになります。
Chart.jsの拡張でもできそうな感じですが、拡張するにあたり調査する時間とラベル部分はどちらにしても自分で書くことになりそうなので作ってしまった方が早いかな、、と。(笑)
久しぶりにCanvasでお絵描きしました。24時間のアナログ時計の文字盤を作る容量ですね。
描画はJavaScriptでしますのでHTML/CSSはcanvasタグの設置とサイズなどのstyleを書くだけですね。
<canvas id="js-graph" class="graph"></canvas>
.graph {
width: 204px;
height: 204px;
}
JavaScriptで要素取得とCanvasサイズなどを設定します。
等倍だとジャギってしまいますので2倍で設定します。
過去時間をグレーにするのでDateの準備もしておきます。
const date = new Date();
const hour = date.getHours();
const graph = document.getElementById('js-graph');
const ctx = graph.getContext('2d');
const gWidth = graph.clientWidth * 2;
graph.width = gWidth;
graph.height = gWidth;
const gCenter = gWidth / 2;
const gRadius = gCenter * 0.77;
const data = [5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 5, 1, 2, 3, 5, 4, 5, 5, 5, 5, 5];
const LEVEL_1 = '#0157a8';
const LEVEL_2 = '#3a8dcc';
const LEVEL_3 = '#4c9c05';
const LEVEL_4 = '#e78200';
const LEVEL_5 = '#e7616c';
function drawPi(ctx, w, c, r, d) {
// ここに描画内容を記述していく
// 時計の部分描画
// データ描画
}
drawPi(ctx, gWidth, gCenter, gRadius, data);
Math.PI = 180°なので円形を描画するのでMath.PI * 2。
それを24分割するので1時間あたりの角度はMath.PI / 12になります。
ただ、このまま描画させると3時(6時)の位置から始まってしまうのでi-6としています。
c * 0.84などの半径はデザインによると思いますので、適宜調整します。
デザイン上、ラベル部分の短線とデータ部分の間に隙間を入れます。
描画部分をクリア、矩形であればclearRectすればいいですが、今回は円形にクリアする必要があります。
その場合は合成方法(globalCompositeOperation)を変更して円形を描画してクリアするようにします。
destination-outとすることで、現在イメージの領域のみが描画され重なった部分は描画されないようになります。
他の合成方法などはCanvasリファレンスをご確認ください。
// 時計の部分描画
ctx.save();
for (let i=0;i<24;i++) {
const angleRad = (Math.PI / 12 )* (i - 6);
const x = (c * 0.84) * Math.cos(angleRad) + c;
const y = (c * 0.84) * Math.sin(angleRad) + c;
const txtX = (c * 0.92) * Math.cos(angleRad) + c;
const txtY = (c * 0.92) * Math.sin(angleRad) + c;
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(c, c);
ctx.closePath();
ctx.restore();
ctx.textAlign = 'center';
if (i < hour) {
ctx.strokeStyle = '#bbb';
ctx.fillStyle = '#bbb';
} else {
ctx.strokeStyle = '#000';
ctx.fillStyle = '#000';
}
ctx.font = '18px din-condensed';
ctx.stroke();
ctx.fillText(String(i)+'時', txtX, txtY + 4);
}
// 中心部分をクリアする
ctx.globalCompositeOperation = 'destination-out';
ctx.beginPath();
ctx.arc(c, c, (c * 0.80), 0, Math.PI*2, false);
ctx.fill();
程の文字盤部分でlineを引いていたのが、扇形を描画することになった以外は基本的には先程の内容と変わりません。
データごとにfillする色が変わりますので、swich文で切り替えています。
データごとに隙間が出てしまいますので、stroke(境界線)を描画することで、これを防ぎます。
// データ描画
ctx.globalCompositeOperation = 'source-over';
ctx.save();
for (let i=0;i<24;i++) {
ctx.beginPath();
ctx.arc(c, c, r, (Math.PI/12)* (i - 5), (Math.PI/12)*(i - 6), true);
ctx.lineTo(c,c);
ctx.closePath();
ctx.restore();
if(d[i]) {
switch (d[i]) {
case 1:
ctx.strokeStyle = LEVEL_1;
ctx.fillStyle = LEVEL_1;
break;
case 2:
ctx.strokeStyle = LEVEL_2;
ctx.fillStyle = LEVEL_2;
break;
case 3:
ctx.strokeStyle = LEVEL_3;
ctx.fillStyle = LEVEL_3;
break;
case 4:
ctx.strokeStyle = LEVEL_4;
ctx.fillStyle = LEVEL_4;
break;
case 5:
ctx.strokeStyle = LEVEL_5;
ctx.fillStyle = LEVEL_5;
break;
default:
break;
}
} else {
ctx.strokeStyle = '#ccc';
ctx.fillStyle = '#ccc';
}
ctx.fill();
ctx.stroke();
}
Canvasというと難しく捉えられがちですが、しっかりと整理して考えればそこまで難しいことはないですね。
ここにアニメーションなど付けようとしたらやることはいろいろとやることは増えると思いますが。
こちらのデモコードはCodePenにアップしていますので良ければ参考にお使いください。