たまに必要に迫られて、ブラウザで完結するように生のJavaScriptを書くことがあります。
今回はそんなシンプルな構成でも案外きれいなUIを作成するのに役立つグラフ作成ライブラリ、Chart.jsについてメモしておきます。
機能の豊富さとコーディングの簡潔さは往々にしてトレードオフなので、ライブラリの選択が重要ですね。
D3ほど高機能でなくてもいいが、できるだけ簡単にグラフを書きたい、という時に扱いやすい(と個人的に思っている)ライブラリです。
■ よく使われるような種類のグラフなら標準の機能だけでも作成できる
■ (ちょっとサイズが大きいが)ファイルひとつで動く
■ デフォルトでアニメーション(パフォーマンス要件が厳しければ無効化することもできる)
もくじ:
Chart.jsの設置
静的コンテンツだけで済むのであれば、ローカルのHTMLファイル一式として置いておけば、サーバを立てられない環境でもブラウザで表示させられて便利です。
ファイル構成はこんな感じにしました。
1 2 3 4 5 6 7 8 9 10 11 | (project root dir) │ index.html │ mycharts.js ←サンプルのスクリプト └─assets ├─css │ bootstrap.min.css └─js bootstrap.min.js bootstrap.min.js.map Chart.bundle.min.js ←公式Githubからダウンロード jquery.min.js |
といっても見た目のためにBootstrapを設置している程度で、最小限にはChart.js本体(Chart.bundle.min.js
)と、そのAPIを呼ぶHTML+Scriptブロックがあれば十分です。ディレクトリ構成もHTML内でパスが通っていれば何でも構いません。
ちゃんとしたインストールはもちろん公式マニュアルの指示通りが推奨。今回はGithubのレポジトリからVersion 2.7.2の本体を入手してスタンドアローンに設置しています。
ちゃんとバックエンドが構築できる環境ではCDNを参照させたり、bowerやらnpmに管理させるなりしたほうが効率的です。
また、時系列を扱う関係でMoment.jsをバンドルしたファイル(Chart.bundle...
)を使っていますが、日付の操作が必要ない場合や別途参照させる場合は単にminifyしたものでOKです。
HTMLはこんな記載にしました。グラフを表示する領域のためのdiv要素と、スクリプトの読み込みをしているだけの記述です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Charts</title> <link rel="stylesheet" href="assets/css/bootstrap.min.css" type="text/css"> </head> <body> <div class="container"> <div class="row"> <!-- chart area --> <div class="col-sm-6"> <div id="chart-area"></div> </div> </div> </div> <!-- scripts --> <script src="assets/js/jquery.min.js"></script> <script src="assets/js/bootstrap.min.js"></script> <script src="assets/js/Chart.bundle.min.js"></script> <script src="mycharts.js"></script> </body> </html> |
別のファイルとして、Chart.jsのAPIを呼び出すためのスクリプトmycharts.js
を用意していますが、この規模かつ生のJavascriptなので何でもアリ、このあたりの組み込み方も特に制約があるわけではありません。もちろんNode、というかCommonJSでは明示的に管理しますが。。。
スクリプトmycharts.js
の詳細は後述しますが、ここでは一旦内容を掲載しておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | // グラフ化するデータ系列のサンプル const sampleData = { labels: ["p0", "p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8", "p9", "p10", "p11"], sample1: [1.9, 2.32, 1.52, 0.79, 1.37, 1.28, 1.92, 1.44, 2.58, -0.01, 0.71, 4.25], sample2: [7.01, -2.15, -7.29, 1.71, 0.72, -4.83, 2.75, 4.11, 3.08, -2.45, 3.05, -3.93], sample3: [-2.97, -2.15, -0.3, -0.65, -8.84, 0.28, 2.95, 2.79, 1.79, 2.87, 0.16, 0.31], timestamp: ["2018/04/16 22:18", "2018/04/16 23:18", "2018/04/17 00:18", "2018/04/17 01:18", "2018/04/17 02:18", "2018/04/17 09:18", "2018/04/17 10:18", "2018/04/17 11:18", "2018/04/17 12:18", "2018/04/17 13:18", "2018/04/17 14:18", "2018/04/17 15:18"] }; // グラフ作成の手順を定義 const loadCharts = function () { const chartDataSet = { type: 'line', data: { labels: sampleData.labels, datasets: [{ label: 'sample1', data: sampleData.sample1, backgroundColor: 'rgba(60, 160, 220, 0.3)', borderColor: 'rgba(60, 160, 220, 0.8)' }, { label: 'sample2', data: sampleData.sample2, backgroundColor: 'rgba(60, 190, 20, 0.3)', borderColor: 'rgba(60, 190, 20, 0.8)' }] }, options: {} }; const ctx = document.createElement('canvas'); document.getElementById('chart-area').appendChild(ctx); new Chart(ctx, chartDataSet); }; // グラフ作成 loadCharts(); |
基本の折れ線グラフ
Chart.jsは棒グラフや円グラフなど、某Excelのテンプレにあるような大抵のグラフはサポートしていますが、その機能のぶん分量が多くなってしまうので、今回は個人的によく使う折れ線グラフについて記載します。
ただし、見た目の調整など他の種類のグラフでも共通的に使える部分も多いです。
今回はmycharts.js
にまとめて記載しますが、大まかな構成はこんな感じ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // グラフ化するデータ系列のサンプル const sampleData = { labels: [...], // ラベル(X軸) sample1: [...], // データ系列 }; // グラフ作成の手順を定義 const loadCharts = function () { const chartDataSet = { type: 'line', // グラフの種類 data: {...}}, // データ系列のセット options: {...} // グラフのオプション }; // divタグの子要素としてcanvas要素を作成 const ctx = document.createElement('canvas'); document.getElementById('chart-area').appendChild(ctx); // API呼び出し new Chart(ctx, chartDataSet); }; // グラフ作成 loadCharts(); |
基本的には必要なデータ(ここではconst chartDataSet
)をセットし、グラフを描画したい領域に確保したcanvas要素を指定してChart.jsのAPIを呼び出す、という手順です。
今回はハードコードしてますが、グラフの元データとなるデータ系列(ここではconst sampleData
)は通常静的に書くことはなく、Ajaxか何かで動的に読むことになるかと思いますが、その場合も手順自体は変わりません。
デフォルトでアニメーション付き、ツールチップ付きなど特に設定を作りこまなくても比較的きれいなグラフが作成できます。下記は上記のmycharts.js
で記載した簡単な例です。
グラフの表示方法により、主にAPIに渡すconst chartDataSet
の作り方にバリエーションがあります。その他の記述は大抵同じなので、以下に異なる部分のみ記載しておきます。
線のスタイル
線の色と背景色
data:datasets[]:borderColor/backgroundColor
折れ線グラフの色については主に線の色borderColor
と背景色backgroundColor
が設定できます。背景色はグラフ領域の塗りつぶしや凡例の塗りつぶし(デフォルト)に使用されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | const chartDataSet = { type: 'line', data: { labels: ..., datasets: [{ label: 'sample1', data: sampleData.sample1, backgroundColor: 'rgba(60, 160, 220, 0.3)', borderColor: 'rgba(60, 160, 220, 0.8)' }, { label: 'sample2', data: sampleData.sample2, backgroundColor: 'rgba(60, 190, 20, 0.3)', borderColor: 'rgba(60, 190, 20, 0.8)' }] }, ... }; |
色(文字列)の書式は上記のようなRGBAのほか、RGBや#RRGGBB、色名が使えます。色の指定方法については他のプロパティでも同じです。
破線
data:datasets[]:borderDash[]
破線にするにはborderDash
に線分の長さと間隔の長さを設定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 | const chartDataSet = { type: 'line', data: { labels: ..., datasets: [{ label: 'sample1', data: sampleData.sample1, borderDash: [8, 2], // set stroke length and spacing length ... },..] }, ... }; |
作成例はこんな感じ(緑色の系列)。
直線
data:datasets[]:lineTension
デフォルトではベジェ曲線で描画され見た目が滑らかな線グラフになりますが、単に直線で各点を繋ぐにはlineTension
をゼロに設定します。
1 2 3 4 5 6 7 8 9 10 11 12 | const chartDataSet = { type: 'line', data: { labels: ..., datasets: [{ label: 'sample1', data: sampleData.sample1, lineTension: 0, // draw straightline },...] }, ... }; |
表示させたもの(水色の系列)はこんな感じ。
ステップ
data:datasets[]:steppedLine
steppedLine
を指定すると、離散値であることを強調するステップ状の線グラフ(↑の図緑色の系列を参照)になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 | const chartDataSet = { type: 'line', data: { labels: ..., datasets: [{ label: 'sample2', data: sampleData.sample2, steppedLine: true, // enable stepped line ..., },...] }, ... }; |
点
data:datasets[]:pointRadius
線グラフの頂点を描画しないように指定するには、pointRadius
をゼロに設定します(先の絵水色の系列を参照)。
1 2 3 4 5 6 7 8 9 10 11 12 13 | const chartDataSet = { type: 'line', data: { labels: ..., datasets: [{ label: 'sample2', data: sampleData.sample2, pointRadius: 0, // hide points ... },...] }, ... }; |
data:datasets[]:pointStyle
また、pointStyle
に指定する文字列により、頂点の形を変更することができます。デフォルトでは円('circle'
)になっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | const chartDataSet = { type: 'line', data: { labels: ..., datasets: [{ label: 'sample1', data: sampleData.sample1, pointStyle: 'rect', // rectangle ... }, { label: 'sample2', data: sampleData.sample2, pointStyle: 'triangle', ... }, { label: 'sample3', data: sampleData.sample3, pointStyle: 'star', ... },...] }, ... }; |
作成例の図は後の塗りつぶしのところにあります。
欠損値
データにNaN
を設定すると、その頂点で途切れた形状になります。
1 2 3 4 5 6 | const chartDataSet = { type: 'line', data: {...}, options: {...} }; chartDataSet.data.datasets[0].data[6] = NaN; // set NaN for dropped data point |
実際に作成させるとこんな感じ。
塗りつぶし(fill)
塗りつぶさない
data:datasets[]:fill
デフォルトの折れ線グラフでは塗りつぶし(fill
)が設定されていますが、同時に表示させるデータ系列が多くなり見づらい場合には、塗りつぶしをさせず頂点・線のみを表示させるよう指定することができます。
fill
にfalse
をセットすると塗りつぶしをさせない設定になります。
1 2 3 4 5 6 7 8 9 10 11 12 | const chartDataSet = { type: 'line', data: { labels: ..., datasets: [{ label: 'sample1', type: 'line', data: sampleData.sample1, fill: false, // not filling with background color ..., }, ... |
塗りつぶしはdatasets
内の各データ系列毎に指定できます。
上側・下側
data:datasets[]:fill
デフォルトでは原点から(X軸との間)塗りつぶしますが、グラフ領域の上端から(end
)または下端(start
)からの面積を塗りつぶすこともできます。
1 2 3 4 5 6 7 8 9 10 11 12 | const chartDataSet = { type: 'line', data: { labels: ..., datasets: [{ label: 'sample1', type: 'line', data: sampleData.sample1, fill: 'end', // fill upper area ..., }, ... |
作成例は下記にあります。頂点が四角の系列の上側を塗りつぶす({fill: 'end'}
)例です。
差分
data:datasets[]:fill
ちょっと変わった塗りつぶしの方法としては、データ系列同士の差となる部分を指定させる設定があります。
対となるデータ系列はデータ系列のインデックスで指定します。fill
に正数を設定した場合はデータ系列の(絶対)インデックス(1, 2…)、文字列を指定した場合は相対インデックス('-1'
:一つ前, '+1'
:一つ後,…)となります。
1 2 3 4 5 6 7 8 9 10 11 12 | const chartDataSet = { type: 'line', data: { labels: ..., datasets: [{ label: 'sample2', type: 'line', data: sampleData.sample2, fill: '+1', // filling with next dataset ..., }, ... |
文章で説明するよりグラフを見たほうがはやいですね。頂点が三角の系列とスターの系列の差となる部分を塗りつぶす例です。
軸の設定
目盛りの設定
options:scales:xAxes[]/yAxes[]:ticks
各X軸・Y軸の設定のなかで、ticks
で各軸の目盛りの設定を指定することができます。
よく使うと思われるのは最大値max
・最小値min
の指定や、開始点をゼロに設定する指定beginAtZero
でしょうか。描画させる範囲を指定することができますが、想定外のデータがセットされた場合は線グラフが見切れてしまったりするので、suggestedMax
・suggestedMin
などの値を使って弱い制約にする指定もあります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | const chartDataSet = { type: 'line', data: {...}, options: { scales: { yAxes: [{ type: 'linear', ticks: { beginAtZero: true, max: 100 } }] }, ... } }; |
グリッドの設定
options:scales:xAxes[]/yAxes[]:gridLines
ここでいうグリッドは、グラフの背景に表示される目盛り毎の線のことです。デフォルトでは灰色の実線で表示されますが、色や線のスタイルを変えることができます。
各X軸・Y軸の設定のなかで、gridLines
プロパティを指定することで書式を変更することができます。
設定可能なプロパティは主軸(zeroLine...
)とその他の目盛りとで名称が別れています。例えば色を設定する場合には、軸はzeroLineColor
プロパティ、その他はcolor
プロパティに指定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | const chartDataSet = { type: 'line', data: {...}, options: { scales: { xAxes: [{ gridLines: { color: 'rgba(251, 214, 58, 0.8)', lineWidth: 4 } }], yAxes: [{ gridLines: { color: 'rgba(251, 61, 49, 0.8)', zeroLineColor: 'rgb(128, 0, 255)', borderDash: [4, 1] } }] }, ... } }; |
上記の指定で作成した例はこんな感じ。
タイトルと凡例
タイトル
options:title
デフォルトではグラフのタイトルが非表示になっているので、明示的に指定({display: true}
)することで表示させることができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 | const chartDataSet = { type: 'line', data: {...}, options: { ..., title: { display: true, text: 'sample line chart', fontSize: 16 }, ... } }; |
作成例は先ほどの絵のようになります。
凡例(legend)
凡例はデフォルトで表示されますが、サイズや位置などを変えることができます。
options:legend
表示位置はposition
プロパティに文字列('top'
, 'right'
…)をセットすることで設定できます。
表示の大きさはlabels.fontSize
プロパティを指定することで調整できますし、凡例の左側に表示されるボックスの幅labels.boxWidth
なども設定することができます。
デフォルトで、ボックスのスタイル(border/fill)はグラフのスタイルを継承するので、塗りつぶさないときでもデータセットにbackgroundColor
を設定しておくといいと思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | const chartDataSet = { type: 'line', data: {...}, options: { legend: { display: true, position: 'left', labels: { fontSize: 18, boxWidth: 10, filter: function (item, chart) { return !item.text.includes('3'); } } }, ... } }; |
さらにfilter
プロパティに自前のFunctionを設定することで、データの値に応じて表示方法を切り替えることもできます。例えば特定のラベルを隠したい場合には、表示させたいものについてtrueを返すFunctionを記述すればOKです。上記の例ではラベルに文字列'3'
を含むもの(ここでは{label: 'sample3'}
の凡例)を非表示にします。
作成例はこんな感じ。
時系列
折れ線グラフにデータ系列をセットするとX軸の値が何であろうと暗黙的に左から右へ並べますが、X軸が時間軸の場合はさらに表示を工夫することができます。
時系列データの場合、各データを不定期に取得した場合など、各頂点を等間隔に並べる表示では正確ではないかもしれません。
明示的に日付型を経由させることで、X軸を時系列として表現します。Chart.jsには含まれないMoment.jsを内部的に使用しているようなので、バンドル版を使うか、別途セットアップする必要があります。
data:datasets[]:data
データ系列data
には、Y軸上の値(y
)と日付型(t
)からなるオブジェクトをセットします。今回はサンプルのデータ系列に文字列で定義した時刻(sampleData.timestamp
)を定義しています。
options:scales:xAxes[]
また、X軸を時間軸として設定するため、定義xAxes
で{type: 'time'}
を指定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | const chartDataSet = { type: 'line', data: { datasets: [{ label: 'sample1 (dist: linear)', data: [], // set data points later ... }] }, options: { scales: { xAxes: [{ type: 'time', // specify time series type distribution: 'linear', // use 'linear'(default) or 'series' ticks: { source: 'data' } }] } } }; for (let i = 0; i < sampleData.sample1.length; i++) { // format data samples to be combined with Date object chartDataSet.data.datasets[0].data.push({ y: sampleData.sample1[i], t: new Date(sampleData.timestamp[i]) // like 'new Data('2018/4/12 03:21')' }); } |
表示方法としては、時刻を表現する設定と、セットされたデータ系列を等間隔に表現する設定があります。時刻型の軸xAxes
内でdistribution
に指定します。
違いは絵で見たほうがはやいと思います。もとのデータ系列は途中大きく時間が空いた(マウスカーソルが映りこんでいる部分)ものをサンプルにしています。よく見ると目盛りの表示間隔が違うことが分かります。
時間軸という意味では取得時刻のばらつきに関わらず時刻スケールで表示({distribution: linear}
)したほうが直感的に理解しやすいものの、あまりデータがない時間が続く場合には間延びしてしまうので、用途によりけりですね。
複数軸
振幅(スケール)の異なるデータ系列を扱うときには、Y軸を左右に分けて表示させたくなる場合があると思います。先に作成例の図を載せるとこんな感じ。
この場合は、Y軸にIDを設定しデータ系列に対応付けることで、半自動的に異なる軸を作図させることができます。
options:scales:yAxes[]
まず、options
で複数のY軸を定義します。yAxes
(配列)に各Y軸を記述し、ID(一意な文字列)や位置('left'
または'right'
)を設定します。
data:datasets[]:yAxisID
各データセットがいずれのY軸に関連付けられるかの設定は、yAxisID
に指定します。単数axisと複数axesでスペルが微妙に変化するのが非英語圏の人間には辛いところですね。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | const chartDataSet = { type: 'line', data: { labels: ..., datasets: [{ label: 'sample1', data: sampleData.sample1, ..., yAxisID: 'y1' // define binding to y-axis }, { label: 'sample2', data: sampleData.sample2, ..., yAxisID: 'y2' // define binding to y-axis }, { label: 'sample3', data: sampleData.sample3, ..., yAxisID: 'y2' // define binding to y-axis }] }, options: { scales: { yAxes: [{ id: 'y1', // set unique name of axis on the left position: 'left', scaleLabel: { display: true, labelString: 'axis y1', fontColor: 'rgb(60, 160, 220)' }, }, { id: 'y2', // set unique name of axis on the right position: 'right', scaleLabel: { display: true, labelString: 'axis y2', fontColor: 'rgb(60, 190, 20)' }, }] } } }; |
また、各Y軸の表示名やサイズなどの書式設定はyAxes
内に書きます。色など見た目で各データ系列と共通点を持たせると見やすくなるかもしれません。
複数種類のグラフ
data:datasets[]:type
datasets
の内側でグラフの種類を指定すると、同じ軸に対して別種類のグラフを組み合わせて表示(混合グラフ)させることができます。
こんな感じのグラフですね。
下記の例では基本の種類に棒グラフ(type: 'bar'
)、1-2番目の系列に折れ線グラフ(type: 'line'
)を指定しています。3番目の系列ではtype
を指定していないので外側の基本設定(ここでは棒グラフ)が適用されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | const chartDataSet = { type: 'bar', // set primary chart type data: { labels: ..., datasets: [{ label: 'sample1', type: 'line', // use secondary chart type data: sampleData.sample1, ... }, { label: 'sample2', type: 'line', // use secondary chart type data: sampleData.sample2, ... }, { label: 'sample3', data: sampleData.sample3, borderWidth: 2, backgroundColor: 'rgba(233, 200, 27, 0.3)', borderColor: 'rgba(233, 200, 27, 0.8)', ... }] }, options: {...} }; |
異なる種類でも、データ系列の形式が大きくことなるようなグラフは組み合わせることができません。例えば折れ線グラフと円グラフとか。
おわり。