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との親和性は高く、マウスオーバーでツールチップを出したり(スマートフォンでは難しそうですが)、アニメーションを追加することもできます。使いこなせば、かなり高度なグラフィックス表現に使えますので、ぜひトライしてみてください。

D3.js - Data-Driven Documents