D3.jsはSVGを使って高度なビジュアル化を行うライブラリです。
グラフに限らず、様々なチャートやビジュアルの表現ができますが、複雑な仕組みを覚えなければなりません。他のグラフライブラリなどではCSVデータを適用するだけというものもありますが、D3.jsはそこまで簡単ではありません。
今回はD3.jsで折れ線グラフ(Line Chart)を描画してみる | Will Style Inc.|神戸にあるウェブ制作会社を参考に、なるべく段階を区切ってD3.jsの使い方を解説します。
インストール
インストールはMonaca IDEのJS/CSSライブラリの管理でD3を検索するか、index.htmlに次の記述を行います。
<script src="https://d3js.org/d3.v5.min.js"></script>
HTML側での準備
HTMLは body
タグ内に次の記述を行います。グラフの大きさはスタイルシートで調整してください。
<div id="chart--wrapper"></div>
<style>
#chart--wrapper {
height: 300px;
}
#chart--wrapper svg {
width: 100%;
height: 100%;
}
</style>
今回はCSVのパース用ライブラリと、JavaScriptを記述するファイルを追加しています。
<!-- CSV解析ライブラリ -->
<script src="https://unpkg.com/papaparse@5.2.0/papaparse.min.js"></script>
<!-- アプリケーション用コード -->
<script src="js/app.js"></script>
JavaScriptについて
www/js/app.js
の内容について解説します。
今回は塗りつぶしまで行ってみます。段階としては次のように分かれます。
D3.js用のDOM取得、については大して説明はいらないでしょう。D3.jsではjQueryのような形でDOMコンテンツを取得したり、作成できます。ここでは先ほどbodyタグ内に記述した #chart--wrapper
の取得と、その中に svg タグを記述しています。
// コンテンツが読み終わったら実行
document.addEventListener('DOMContentLoaded', async (e) => {
// D3.js用のDOM取得
const contents = d3.select('#chart--wrapper');
const svg = contents.append("svg");
// 1. データを取得
const data = await getData();
// 2. データを整形する
const dataset = filterData(data.data);
// 3. グラフの枠を準備する
const scale = setupGraph(contents, svg, dataset);
// 4. 折れ線グラフを描画
const color = d3.rgb("#a785cc");
drawLine(svg, dataset, scale, color);
// 5. 塗りつぶし
drawFill(svg, dataset, scale, color);
});
1. データを取得
次は、データの取得を行う getData 関数についての解説です。
今回は機械学習によく使われるAAPL.CSVを使っています。このファイルは様々な場所で見かけますが、GitHubからダウンロードもできます。これを www/data/aapl.csv
として保存します。
このデータを取得し、CSV解析ライブラリでテキストデータから配列に変換します。このステップは外部のWeb APIなどからデータを取得する場合には不要でしょう。
async function getData() {
return new Promise((res, rej) => {
Papa.parse('./data/aapl.csv', {
download: true,
header: true,
delimiter: ',',
complete: res,
error: rej
});
})
}
このデータの概要は以下のようになっています。日付が Date カラムに入っており、Open/Close/High/Lowというデータがあります。
2. データを整形する
次にデータを整形します。
日付は YYYY-MM-DD
という形式になっていますので d3.timeParse
を使って変換します。さらに値もCSVで解析したままでは文字列なので parseFloat
を使って数値にします。そしてデータの中に日付が入っていない不正なものも含まれていたので、filterを使って除外しています。
function filterData(data) {
const timeparser = d3.timeParse("%Y-%m-%d");
return data.map(d => {
return {
date: timeparser(d.Date),
value: parseFloat(d.Open)
};
})
.filter(d => d.date != null); // 不要なデータを除外
}
この下準備でD3.jsで使えるデータになります。
3. グラフの枠を準備する
D3.jsではデータを渡せば後は勝手に表示してくれるという訳ではありません。まずSVGを用意したのと同様に、グラフを描画する枠を準備します。これはもちろんX軸、Y軸の二つがあります。各コードの内容はコメントを参考にしてください。
function setupGraph(contents, svg, dataset) {
const padding = 30;
// グラフの幅(X軸)
const width = contents.node().clientWidth - padding;
// グラフの高さ(Y軸)
const height = contents.node().clientHeight - padding;
// gはグループです。SVGで他の要素をグループ化する際に利用するタグです
// グループをXとY、両方に追加します。
x = svg
.append("g")
.attr("class", "axis axis-x")
y = svg
.append("g")
.attr("class", "axis axis-y")
// X軸に関する設定です
const xScale = d3
// 時間ベースの軸であることを定義します
.scaleTime()
// 最低値、最大値の設定です
.domain([
d3.min(dataset.map(d => d.date)), // 日付の配列を渡せば、d3.minで最小値を出してくれます
d3.max(dataset.map(d => d.date)) // 日付の配列を渡せば、d3.maxで最大値を出してくれます
])
// 大きさを指定します
.range([padding, width]);
// Y軸に関する設定です
const yScale = d3
// 数値ベースの軸であることを定義します
.scaleLinear()
// 最低値、最大値の設定です
.domain([
0,
d3.max(dataset.map(d => d.value)) // 値の配列を渡せば、d3.maxで最大値を出してくれます
])
// 大きさを指定します
.range([height, padding]);
// 表示する際のフォーマットです(YYYY/MMという表示です)
const format = d3.timeFormat("%Y/%m");
// X軸の値の個数です
const xTicks = 6;
// X軸の記述設定です
const axisx = d3
.axisBottom(xScale)
.ticks(xTicks)
.tickFormat(format);
// Y軸の記述設定です
const axisy = d3.axisLeft(yScale);
// SVGに描画します
x
.attr("transform", "translate(" + 0 + "," + (height) + ")")
.call(axisx);
y
.attr("transform", "translate(" + padding + "," + 0 + ")")
.call(axisy);
// xScale/yScaleはこの後も使うので返却します
return {x: xScale, y: yScale};
}
この結果として、グラフの枠だけが表示されます。
4. 折れ線グラフを描画
グラフの枠はできあがったので、値を折れ線グラフとして描画していきます。ここは先ほどのグラフ枠の生成に比べるとシンプルです。こちらもコードはコメントを参考にしてください。
function drawLine(svg, dataset, scale, color) {
// pathはSVGで図形を描画する汎用的な要素です
const path = svg.append("path");
// 折れ線を生成します
const line = d3
.line()
.x(d => scale.x(d.date))
.y(d => scale.y(d.value));
// SVG上に描画します。colorは引数で受け取っている、色の指定です
path
.datum(dataset)
.attr("fill", "none")
.attr("stroke", color)
.attr("d", line);
}
これで折れ線グラフが描画できました。
5. 塗りつぶし
最後にグラデーションを使った塗りつぶしを行ってみます。これは、上が線と同じ色、下は白とグラデーションで表示する方法です。見ると分かりますが、上半分はSVGに関する記述になります。
function drawFill(svg, dataset, scale, color) {
// SVGのpathを用意
const lineArea = svg.append("path");
// SVGのグループも用意
const g = svg.append("g");
// defsはSVGの描画オブジェクトを定義し、再利用できるようにします
// 今回であればlinearGradient(塗りをサポートした要素)がそうです
const linearGradient = svg
.append("defs")
.append("linearGradient")
// attrはjQueryのように要素を追加します
.attr("id", "linear-gradient")
.attr("gradientTransform", "rotate(90)");
// 上の色を定義します
// stopはSVGのグラデーションを決める要素です
linearGradient
.append("stop")
.attr("offset", "40%")
.attr("stop-color",color.brighter(0.5));
// 下の色を定義します
linearGradient
.append("stop")
.attr("offset", "60%")
.attr("stop-color",color.brighter(1.7));
// ここからD3.jsの記述です
// 線を描く際には line でしたが、今回は area で塗りつぶしを指定します
const area = d3
.area()
.x(d => scale.x(d.date))
.y1(d => scale.y(d.value))
.y0(scale.y(0))
// curveCatmullRomは曲線を描くためのアルゴリズムです
.curve(d3.curveCatmullRom.alpha(0.4));
// SVGに描画します
lineArea
.datum(dataset)
.attr("d",area)
.style("fill", "url(#linear-gradient)");
}
このようにすると、グラフにグラデーションをかけられます。
まとめ
D3.jsを使いこなす上で大事なのはSVGを把握することです。D3.jsによってSVGが幾分使いやすくなっていますが、それでも基本的なSVGを分かっていないとどう使っていいかも分からないでしょう。そしてSVGのセットアップができたら、D3.jsのオブジェクトを作成し、SVGとD3.jsオブジェクトをミックスして描画するといった形です。
基本的にSVGなので、JavaScriptとの親和性は高く、マウスオーバーでツールチップを出したり(スマートフォンでは難しそうですが)、アニメーションを追加することもできます。使いこなせば、かなり高度なグラフィックス表現に使えますので、ぜひトライしてみてください。