Help us understand the problem. What is going on with this article?

これから D3.js を始める人のためのメモ

はじめに

私はもともとネットワークとかインフラ系の話をやっている人で、ソフトウェア開発はやっていなかったのですが、この 1 年くらいで D3.js とか Vue.js とかを触るようになりました。この記事では、私の体験をもとに、フロントエンド技術が主業務ではない人 (私のようなIT基盤系エンジニアなど) 向けに、D3.js をつかったデータ可視化をする際の考え方をお伝えしたいと思います。プログラミングのノウハウというよりは、プログラミングをする前に知っておくべきこと・考えておくべきこと、みたいな形になるので、個人差があるとは思いますが、まあ参考程度に。

D3.js を始める前に

[2019/9月時点情報]

D3.js は v3 と v4/v5 の間に大きな変更が入っています。 d3/CHANGES.md をみてもらえるとわかると思うのですが、v4 (2016/6) の変更箇所の多さよ……。このへん 細かすぎて伝わらないD3 ver.4の話 という資料がいろいろ書いてくれているので一度目を通してください。モジュール (NPMパッケージ) 構成や、名前空間、APIの名前そのものなど多くの変更があります。今後メンテするつもりがなくてとりあえず単発で今だけ動けばいいプログラムであれば v3 でもまあよいのかなとは思いますが、今後も D3 使っていこうと思うのであれば v4 以降で覚えるべきでしょう。いま v3 で覚えてしまうと、変更点をがっちり調べて v4 以降のプログラムに書き換えることになり、これはかなり面倒だと思います。

私の観測範囲 (2019/4 月前後) では、データビジュアライゼーションなどの書籍で D3.js を扱っているもののほとんどが v3 対応で v4/v5 に対応した解説があるものはおそらくでてないかったと思います1。ただ、そうした書籍は「データビジュアライゼーション」が本題であり、そのための道具として D3.js について解説しているということがほとんどなので、D3.js のバージョンそのものはあまり本の価値に影響しないかもしれません。もし D3.js の使い方や実践的知識に期待して買うのであれば v4 対応しているものを探したほうが良いです (それか D3.js 関連部分については Web で補うか)。

D3.js Gallery などサンプルとして公開されている例の多くは v4 以降になってきました。が、まだ v3 のものもあります。Web 検索でサンプル探すような際は D3.js のバージョンに注意しましょう。

参考

D3.js の入り口として参考になるサイト

D3.js はなにをするものなのかを理解する

D3 = Data Driven Document ってことなんですが、"Data Driven" と "Document" の話を理解しておく必要があります。

Document

まずは、"Document" のところから。D3.js は何を操作してどうやって「可視化」しているのかを知りましょう。Webブラウザ上でいろいろな「図形」を「インタラクティブ」に動かすためにどんな知識が必要なのか?

  • HTML5, CSS3
  • JavaScript
  • SVG

まあ Web フロントエンドの話なのでそりゃそうだろ、という話なんですが。フロントエンド技術になじみがないとね、こういう話からフォローしなきゃいかんのですよね。特に D3.js をやるまでたいして気にしたことがないであろう SVG についてです。

まずは以下のコードがどう表示されるのかを理解しましょう

const circles = [
  { type: 'small', r: 50, x: 100, y: 150 },
  { type: 'medium', r: 100, x: 200, y: 150 },
  { type: 'large', r: 150, x: 300, y: 150 }  
]
const svg = d3.select('#circles')
  .append('svg')
  .attr('width', 500)
  .attr('height', 500)
svg.selectAll('circles')
  .data(circles)
  .enter()
  .append('circle')
  .attr('r', d => d.r)
  .attr('cx', d => d.x)
  .attr('cy', d => d.y)
  .attr('class', d => d.type)

これがこういうコード(HTML/SVG)になって

<svg width="500" height="500">
  <circle r="50" cx="100" cy="150" class="small"></circle>
  <circle r="100" cx="200" cy="150" class="medium"></circle>
  <circle r="150" cx="300" cy="150" class="large"></circle>
</svg>

ブラウザ上ではこう表示されます。

fig1.png

See the Pen d3.js basic by m.hagiwara (@corestate55) on CodePen.

何が言いたいかというと、ブラウザで表示されている図形はいろいろな情報が組み合わさっているってことです。

fig2.png

SVG については、詳しくはほかの資料を参照してもらうとして、見てわかる通り、これは Vector Graphics を定義する XML ベースの "Document" になります。なので、JavaScript ではほかの HTML Document と同じように操作ができる。

D3.js はデータをもとに ("Data Driven") SVG ("Document") を作るための機能を持っています。SVG をうまく作るためのライブラリなので、SVG でできることや SVG の仕様が分かっていないと、D3.js でできることや D3.js API の意味などが理解できなかったりします。また、ブラウザ上での表示や操作 (インタラクティブ性) というところまで含めると、D3.js (SVG) 単体の機能だけではなく、CSS や JavaScript による DOM 操作 (イベントハンドリング) などを組み合わせることになります。たいてい、ひとつのことを実現するために複数の手段があるので、何を使ってどこまでやるのかを考える必要があります。

参考

Data Driven

"Document" の話をしたので、次は "Data Driven" のところ。D3.js で SVG を作成するときの基本的なオペレーションは以下のような流れになります。

  • 操作対象になる DOM を選択する (select)
  • 操作対象にデータを紐づける (data-update/enter/exit)
  • 操作対象に設定する属性と、属性値 (属性値を決めるためのコールバック関数) を定義する

紐づけられたデータに対して各コールバックが呼び出され、要素の追加・削除や属性値の設定・変更などが行われます。すなわち、すべての操作が、data で紐づけられるデータによって規定されるということです。用意するデータ構造が適当でいい加減だと処理が非常にややこしくなります。つまり、

  • ブラウザ上でどう表示するか / (利用者が)どんな操作をするか
    • そのためには SVG 要素としてどんなものが必要か
    • 実現したい見せ方にするためにはどんな属性やスタイルを設定する必要があるか
    • 「見せ方」を規定するためにどんな情報が必要か
    • それらの情報はどのようなデータ構造に格納しておくとよいか

というのを順を追って考えておく必要があるわけです。(……というあたりが作ってみないといまひとつピンとこなかったです。) ひとつ理解しておくべきなのは、可視化したい情報 (元データ) と表示するために必要な情報は、直接には一致しないということです。

例えば、年度ごとの売り上げを棒グラフにしようというときに、まず用意する元データは 年, 売上額 の組 (CSV などで定義された表データ) でしょう。でもこれを SVG で棒グラフとして表現するためには、

  • 棒になる四角形を描くための情報 → 四角形左上の x,y 座標と幅・高さ
  • 棒の下につけるラベルを描くための情報 → テキストを付ける x,y 座標とラベルテキスト
  • 軸と目盛を描くための情報 → 軸の始点・終点座標、軸ラインの幅や線種、目盛間隔
  • 図全体が SVG サイズに収まるようにするための拡大・縮小比率計算

などが必要になります。どんな形でデータを可視化したいのか → それは SVG のどんな要素を使えば表現できそうか、その要素を作るために必要な情報は元データからどうやれば計算できるか……と順に検討して、必要なデータやデータ処理方法をそろえていくことが D3.js アプリケーションの設計において考えることになります。

売上棒グラフの例だと、もとの 年, 売上額 の CSV データをもとにこれらの情報を補完しつつ、最終的に実現したい「図」をどんな SVG element(s) で実現するのか、その element(s) を作るためにはにはどういったデータ構造や属性値設定(コールバック関数)が必要か、というところですね。ブラウザ上でインタラクティブに操作をしたいのであればさらに、実現したい動作 → SVG element にどういったイベントハンドラを設定してどの element を動かせばいいか、というところも加味することになります。

D3.js でひっかかるところ

要素の選択・名前付け

D3.js で SVG 要素を作ったり消したり属性値を変えたりするときに必要になるのが、SVG 要素に対する class, id の設定 (どんな名前を付けるか) ですね。「操作対象の要素を選択する」が起点になるので名前重要です。D3.js では複数の要素を選択してまとめて処理をすることができます。その選択に 要素名, id, class を指定することになりますが、操作対象ごとにちゃんと 要素名/id/class を指定できること、というのがそのあとプログラムを書く工程に響いてきます。

あと、この辺は HTML/CSS 的な知識になりますが、下記の点ちゃんと検討しましょう。

  • 複数まとめて操作する要素は何か
    • class は複数指定できる
    • イベントに応じて class を付けたり外したりする (class を変えることによって見た目や動きを制御する) ことができる
  • 単独で操作される要素は何か
    • id はHTMLドキュメント内全体で一意でなければいけない

特に、インタラクティブなものを作る場合、特定の要素を指定 → それを含む複数のグループに属するオブジェクトを処理、みたいな、単独要素から複数要素にターゲットが移る操作なんかはよくあります。画面にどんな要素を表示するのか・どのように操作するのかというところで、こうしたグルーピング処理を洗い出せていないと、うまくデータ設計や要素選択ができなくてプログラムが非常に煩雑になってしまいます。

インタラクティブな操作と動的な状態の管理

表示した図に対してユーザから操作を行って、動的に図を変化させるインタラクティブな処理ができます。このとき、図に対するコントロールが複数あるような場合、インタラクティブな処理によって変化する動的なステータスをどう保持するかを検討しておく必要があります。

例えば、あるデータを棒グラフで表示した後、追加で同じデータの円グラフ表示を表示できるようになっていたとします。棒グラフは、「棒」をクリックするとハイライトされて色が変わるようになっています。あとから円グラフを追加したときに、棒グラフでハイライトされているデータに相当する部分 (円グラフのパイ) が同じようにハイライトされてほしい、みたいなケースを考えてみましょう。棒グラフではクリックイベントに対してクリックされた SVG element (某) がハイライトするようになっていましたが、これはあとから追加された円グラフには特に影響を及ぼしません。円グラフでも選択したデータがハイライトされていてほしいのであれば、円グラフの元データにハイライトされているという情報が含まれている必要があります。

図に対する操作がひとつ (一方向) であればは元データに反映させなくても特に問題ありません。しかし、複数のコントロールがあって操作状態を同期させたいようなケースでは、元データに操作ステータスを保持して、同じデータからハイライトされているという操作状態までを含めて描画できるようにしておく必要があります。図として表現したいことについてはデータとして保持して、「データから図が定義される」形 (data driven) にしましょう。

SVG要素の順番

D3.js で図を作っててふと問題になるのが、描画順序 (要素重ね合わせの順序) ですね。基本的に SVG ドキュメントとして登場した順で描画されるので、最後に書かれたものが一番上に来ます (上に出した円を三つ描く図を参照)。複数の要素を重ね合わせて図を作るような場合には、それらの重ね合わせ順序などを気にしながら描画用のデータを組み立てておくなどの工夫が必要になったりします。

例えば、要素重ね合わせ順番をデータに含めておいて、描画時に配列ソートしてから描画するとか、あるいはパートごとに g でグルーピングしてドキュメント内の挿入位置(順番)をある程度決めておくこともできるかもしれません。

データの操作

D3.js API では「こういうデータ構造でデータを作って食わせてくれ」というのが決まっているものがあります。そうしたものにはデータ変換のための API まで合わせて用意されていたりします。自力でデータ変換処理を作るよりはそうした API をうまく使えるようにしたほうが良いでしょう。

例えば d3-hierarchy は木構造データを扱うための API があります。木構造としての基本データ構造(属性)は規定されていて、それにのっとっていればここにあるいろんなデータ操作 API が使える。SVG で複数の要素を作る場合、配列にデータをまとめて SVG 要素に binding することになるんですが、木構造のデータを配列に変換するとか、結構面倒なデータ操作が必要になるので、提供されている機能を使いましょう。また、そうした機能を使えるような元データにしておくことも合わせて考えましょう。

とはいえ、一般的なデータ構造のために抽象化されているところとか、お約束的な操作についていけないと D3.js オフィシャルのマニュアル読んでもよくわからなかったりするんですよね。例えば、ユーザが任意に規定するデータ属性の参照については、アクセサを callback function として渡すとか、D3.js API が処理用の関数を返す(ジェネレータ)とか。そうした抽象的な操作に気をつけてみてみると良いと思います。

JavaScript

この辺の話しちゃうとまた長くなる気がするのでざっと個人的に引っかかったポイントだけ: ES2015(ES6) 以降の書き方をちゃんと覚えましょう!

  • class を使ったオブジェクト指向プログラミングができるので他の OOP やってた人はそこそこ入りやすいはず。
    • Javascript における Object は何かを理解しましょう
  • コールバック関数(closure)の役割・使い方を覚えましょう。
    • arrow function と function () の違いとかね……
    • ユーザが任意に規定する処理をコールバックとして渡すという処理はいたるところで出てきます。非同期処理とかの記述でも頻繁に出てきます。基本パターンのひとつとして使い方を覚えましょう。
    • this 問題: D3 API 等に渡すコールバック関数の中では this の話が出てくるのですが、ちゃんと理解しないで使うとたいへん混乱を招くので気をつけましょう。
  • ブラウザサポートの話は Babel に任せましょう。
    • → 開発環境の話へ; いまから覚えるなら新しい言語仕様を覚えた方がいいと思う。
  • 非同期処理と Promise について理解しましょう

参考

開発環境

最初は script タグで HTML に直接書く形で良いのですが、ある程度のサイズになると NPM や Webpack を使った環境構築が必要になってきます。しかし、これは Web フロントエンドの開発始めてやるという人にはなじみがなくてとっつきにくいんですよね。フロントエンド開発界隈は変化が速いので、Web 検索でこの辺の情報を集める場合は、それがいつ時点の情報なのかに注意してください。

参考

おわりに

なぜインフラやってた人が D3.js をさわり始めたかというと、インフラやってると、脳内でイメージ合成しているような作業が結構あるんですよね。あの構成図といまのコマンドの出力 (現在の状態) を合わせると、ここでアレが起きているはずだ、みたいな作業が。人が頭の中で合成しているイメージって、他の人からはわからないし、認識がずれるし、スケールしないし、結局そこ (脳内イメージが合成できる人) がボトルネックになってしまう。そういうところを可視化できるともうちょっといろいろやりやすくなるんじゃないかな、というのが理由です。

この記事では実際に D3.js のプログラムを作ってみて気付いたことを (あまりうまくまとまってないけど) 書き出してみたつもりです。とはいえ、これを読んだからといって D3.js によるデータ可視化がさらっとやれるようになるわけでもないとは思いますが…。これからやるときに、こういう話を覚えておくとスムーズになるかも、くらいで見てもらえればいいんじゃないかなと思います。


  1. この記事は当初 2019/4 月くらいに下書きを書いているので記事公開日時とズレがあります。なお、洋書は調べていません。 

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away