D3.js / v5 の基本的な使い方
[履歴] [最終更新] (2018/09/18 23:56:59)

概要

データをもとにして DOM を操作する D3.js (Data-Driven Documents) の基本的な使い方を記載します。特にバージョンは v5 を対象とします。

Hello world

HTTP サーバ

外部からデータを読み込むために CORS を考慮する必要があります。D3.js の動作検証時には簡単な HTTP サーバを立てる必要があります。Python を利用する場合は以下のようにします。

python2 -m SimpleHTTPServer 8888
python3 -m http.server 8888

http://localhost:8888/path/to/sample.html で動作確認できるようになります。

インストール

CDN を利用する場合は以下のいずれかを指定します。

<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://d3js.org/d3.v5.js"></script>

GitHub からリリース zip ファイルをダウンロードして利用することもできます。

npm/yarn 経由でインストールすることもできます。

npm view d3 versions --json

動作確認

CSS セレクタで要素を指定します。jQuery 等と同様です。data に応じて DOM の font-size を変更しています。jQuery 等と同様に D3 は DOM ツリーが完成した後に実行する必要があります。例えば以下のように body の最後に script を記述します。

sample.html

<html>
<head>
  <script src="https://d3js.org/d3.v5.min.js"></script>
</head>
<body style="background:#fafafa">
  <p>1</p>
  <p>2</p>
  <p>3</p>
  <p>4</p>
  <p>5</p>
  <script>
   d3.selectAll("p")
     .data([4, 8, 15, 16, 23])
     .style("font-size", function(d) { return d + "px"; });
  </script>
</body>
</html>

Uploaded Image

新規データによる既存 DOM の更新

  • 既存 DOM の要素を新規データで更新するためには単純に select して処理すればよいです。
  • 既存 DOM の要素数が新規データ数よりも少ない場合は enter().append() で追加できます。
  • 既存 DOM の要素数が新規データ数よりも多い場合は exit().remove() で削除できます。

既存 DOM の 999 を新規データで更新した後に、新規 DOM の追加またはデータ数よりも多い既存 DOM の削除を行っています。

<html>
<head>
  <script src="https://d3js.org/d3.v5.min.js"></script>
</head>
<body style="background:#fafafa">
  <div id="myid1">
    <p>999</p>
  </div>
  <div id="myid2">
    <p>999</p>
    <p>999</p>
    <p>999</p>
    <p>999</p>
  </div>
  <script>
   /////////////////////////////////
   // Update
   var p1 = d3.select("div#myid1")
             .selectAll("p")
             .data([4, 8])
             .text(function(d) { return "updated: " + d; });
   // Enter
   p1.enter().append("p")
     .text(function(d) { return "added: " + d; });
   // Exit
   p1.exit().remove();

   /////////////////////////////////
   // Update
   var p2 = d3.select("div#myid2")
             .selectAll("p")
             .data([4, 8])
             .text(function(d) { return "updated: " + d; });
   // Enter
   p2.enter().append("p")
     .text(function(d) { return "added: " + d; });
   // Exit
   p2.exit().remove();
  </script>
</body>
</html>

Uploaded Image

遷移

1000ミリ秒 delay 待機してから、1000ミリ秒 duration 期間で font-size 100px に遷移します。

<html>
<head>
  <script src="https://d3js.org/d3.v5.min.js"></script>
</head>
<body style="background:#fafafa">
  <p>hi</p>
  <script>
   d3.select("p")
     .transition()
     .duration(1000)
     .delay(1000)
     .style("font-size", "100px");
  </script>
</body>
</html>

遷移前

Uploaded Image

遷移後

Uploaded Image

属性値の変更

CSS 値ではなく HTML の属性値を変更する場合は attr を利用します。

d3.select("p")
  .attr("class", "myclass");

TSV から折れ線グラフを生成して描画

D3 v4 のサンプルコードを v5 用に変換してあります。D3 を利用すると細かな制御が可能ですが、複雑なことをせずに表示するだけであれば plotly.js 等を利用すると簡単です。

Uploaded Image

<html>
<head>
<script src="https://d3js.org/d3.v5.min.js"></script>
<style>
 .axis--x path {
   display: none;
 }
 .line {
   fill: none;
   stroke: steelblue;
   stroke-width: 1.5px;
 }
</style>
</head>
<body style="background:#fafafa">

<!-- 以下の svg タグを D3 から操作します -->
<svg width="960" height="500"></svg>

<script>

// svg タグ
var svg = d3.select("svg");

// グラフ本体のサイズを計算
var margin = {top: 20, right: 80, bottom: 30, left: 50};
var width = svg.attr("width") - margin.left - margin.right;
var height = svg.attr("height") - margin.top - margin.bottom;

// svg タグ内で利用する div のようなコンテナ要素 'g' https://developer.mozilla.org/ja/docs/Web/SVG/Element/g
// グラフ本体 g を移動させます https://developer.mozilla.org/ja/docs/Web/SVG/Attribute/transform
var g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");

// ユーティリティ関数を定義 https://github.com/d3/d3-time-format
// Date を返します https://www.qoosky.io/techs/50044defab
var parseTime = d3.timeParse("%Y%m%d");
//var parseTime = d3.timeParse("%s"); // タイムスタンプの場合

// 入力データの区間 domain[from, to] を
// グラフ上の区間 range[from, to] に変換 scale するための関数を定義
// https://github.com/d3/d3-scale/blob/master/README.md
var xx = d3.scaleTime().range([0, width]);
var yy = d3.scaleLinear().range([height, 0]);
// 折れ線の色を変えるために range を `d3.schemeCategory10` に指定
// https://github.com/d3/d3-scale-chromatic/blob/master/README.md#schemeCategory10
var zz = d3.scaleOrdinal(d3.schemeCategory10);

// データから折れ線を生成するための関数を定義
// [{ date: "20111001", temperature: 63.4 }]
// という形式の配列の要素をグラフ上の点に変換する .x() と .y() を設定
// https://github.com/d3/d3-shape/blob/master/README.md
var line = d3.line()
    .curve(d3.curveBasis)
    .x(function(d) { return xx(d.date); })
    .y(function(d) { return yy(d.temperature); });

// TSV を読み込んで処理
// (非同期処理のプロミスが利用されています https://www.qoosky.io/techs/cf38dc8583 )
d3.tsv("http://localhost:8888/data.tsv").then(function(data) {

  // TSV を利用しやすい形式に変換
  // [{"date":"20111001", "New York":"63.4", "San Francisco":"62.7", "Austin":"72.2"}]
  // slice: https://www.qoosky.io/techs/0366645194#slice
  var cities = data.columns.slice(1).map(function(cityName) {
    return {
      id: cityName,
      values: data.map(function(d) {
        return {date: parseTime(d.date), temperature: d[cityName]};
      })
    };
  });

  // X 軸の入力データ幅を設定
  // https://github.com/d3/d3-array/blob/master/README.md#extent
  xx.domain(d3.extent(data, function(d) { return parseTime(d.date); }));

  // Y 軸の入力データ幅を設定
  yy.domain([
    d3.min(cities, function(c) { return d3.min(c.values, function(d) { return d.temperature; }); }),
    d3.max(cities, function(c) { return d3.max(c.values, function(d) { return d.temperature; }); })
  ]);

  // 複数のグラフ (Z 軸の入力は cityName とします)
  zz.domain(cities.map(function(c) { return c.id; }));

  // 横軸のラベルを追加
  g.append("g")
      .attr("class", "axis axis--x")
      .attr("transform", "translate(0," + height + ")")
      .call(d3.axisBottom(xx));

  // 縦軸のラベルを追加
  // https://developer.mozilla.org/ja/docs/Web/SVG/Element/text
  g.append("g")
      .attr("class", "axis axis--y")
      .call(d3.axisLeft(yy))
    .append("text")
      .attr("transform", "rotate(-90)")
      .attr("y", 15) // グラフから 15px 離します
      .attr("fill", "#000")
      .text("Temperature, ºF");

  // TSV から加工した cities データの種類分だけ g を追加
  var city = g.selectAll(".city")
    .data(cities)
    .enter().append("g")
      .attr("class", "city");

  // 折れ線として SVG の path 要素を追加 https://developer.mozilla.org/ja/docs/Web/SVG/Element/path
  city.append("path")
      .attr("class", "line")
      .attr("d", function(d) { return line(d.values); })
      .style("stroke", function(d) { return zz(d.id); });

  // 折れ線のラベルとして SVG の text 要素を追加 https://developer.mozilla.org/ja/docs/Web/SVG/Element/text
  city.append("text")
      .datum(function(d) { return {id: d.id, value: d.values[d.values.length - 1]}; }) // 各 city の最後の要素
      .attr("transform", function(d) { return "translate(" + xx(d.value.date) + "," + yy(d.value.temperature) + ")"; })
      .attr("x", 5) // 5px
      .style("font", "10px sans-serif")
      .text(function(d) { return d.id; });
});
</script>
</body>
</html>

data.tsv

date    New York    San Francisco   Austin
20111001    63.4    62.7    72.2
20111002    58.0    59.9    67.7
20111003    53.3    59.1    69.4
...

20120928    68.7    52.6    77.2
20120929    62.5    53.9    75.2
20120930    62.3    55.1    71.9
関連ページ
    概要 React 16.8 で導入された Hook のサンプルコードを記載します。 コンポーネントの機能を共有するための手法であった Render Props や Higher-Order Components を利用する必要がなくなります。複数コンポーネントでの状態共有も簡単になります。 componentDidMount