こんにちは、食戟のソーマにどハマりしているフロントエンドの小松です。
今回はタイトル通りD3.jsを一行ずつ読み解きながらグラフ実装を進めていきます!
「D3.jsってなんだろう?」という方はこのぜひこの記事を最後まで読んで頂ければと思います。
※ちなみにD3のversionは4系で進めます
目次
D3.jsとは
D3.js(以下D3)のD3は、「Data-Driven Documents」の頭文字をとって命名されているようです。
日本語で言うなら「データ駆動型ドキュメント」的な…日本語だとちょっとよくわからないのですが、とりあえずJavaScript製のデータ可視化ライブラリになります。
この2年間くらいのトレンドを見てみると熱心なユーザーとコミュニティのおかげで衰退することなく使われ続けているようです。
なので、今から学習しておいて損はしないかと思われます!
そんな素敵なD3ですが、残念ながら初めはとっつきにくい奴なのです…
個人的な感想ですが、ライブラリというほどお手軽ではなく、D3の記法やルール・考え方が独自なので初めは飲み込みづらかったです。
なので、今回は自分の理解を深めるためにも一行ずつ理解しながら進めてきたいと思います!
何ができるのか
まず初めに、D3はグラフ描画ライブラリではありません。
Data-Driven Documentsという名前の通り、あくまでデータ可視化をサポートしてくれるライブラリになります。
なので「俺は手っ取り早くグラフを描画したいんだ!!」という方にはC3.jsをおすすめします。
ほとんどの場合はC3.jsやらChart.jsなどを使った方が学習コストも低いので無理してD3.jsを使う必要はないかと思います。
それでもなぜD3を使うのか…それはD3が他のデータビジュアライズライブラリと比べてとてもシンプルで柔軟な作りになっているからです。
つまりD3を理解さえすれば、想像しうる限りの複雑なグラフはもちろん、このサンプルページのように面白い使い方もできます。
バージョンについて
ちなみにD3は2016年にversion3から4にメジャーアップデートしました。
素晴らしいことなのですが、変更がかなり大きく、世の中に出回っているD3(v3系)の書籍や記事はほとんど過去のものとなってしまいました…
基本的な考え方は同じなのですが、頻繁に使うメソッドにも影響が出ているため、これからv4を使われる方はくれぐれもv3のドキュメントを見ながら作業をしないようお気をつけください。
そんなわけで今回はv4を使って学習を進めていきましょう!
(実はこのブログ、v3で書いた後にv4に書き直しました…笑)
D3.jsを使うための準備
では早速D3を使ってみましょう。
JSFiddleはD3対応しているのですが、なぜかv3しかないため今回は下記のスクリプトからv4を読み込んでおきます。
<script src="https://d3js.org/d3.v4.js"></script>
実際にプロジェクトで使う際はD3のコアjsをダウンロードするかCDNで呼び出すか、またはnpmなどで管理することも可能です。
そこらへんは公式サイトに記載されていますので、手順に沿ってD3を読み込みましょう。
まずはsvgを作りましょう
D3はsvg,canvas,HTML上にデータを可視化していきます。
つまり初めに土台(今回はsvg)が必要になりますが、これはHTMLに自分であらかじめsvg要素を記述して用意しても、D3経由で生成しても構いません。
今回はせっかくなのでsvgもD3で作成して進めていきましょう。
JavaScriptの記述を一行ずつ追ってみます!
要素を選択
var svg = d3.select("body")
d3オブジェクトの中に読み込んだライブラリが詰まっており、その中の select
をbody要素に対して使用しています。
つまり、現時点では svg
という名前の変数にbody要素が格納されています。
考え方としてはjQueryと同じで、この場合は var svg = $('body')
のような感じです。
要素を追加
var svg = d3.select("body")
.append("svg")
bodyが格納された svg
に対して、 d3.append
を用いてsvgタグを追加します。
これでbodyの中にsvgが追加されました!
ちなみにjQueryなら svg.append('svg')
ですね。
属性を設定
var svg = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 300);
最後に attr
を使って追加したsvgタグに対して width
と height
を設定します。
これで横幅500px・縦幅300pxのsvgが完成しました!
svgの生成完成
実際にここまでをJSFiddleで走らせると、このように真っさらなsvgが追加されています。
(説明の都合上svgに対して白色の背景色を設定しています)
図形を描画してみましょう
svgはいわばキャンバスのようなもので、これからこの上に様々な要素を描くことができます。
D3本来の強みであるデータ可視化に入る前に、D3で比較的簡単に実現できる図形(circle
)を描画してみたいと思います!
ちなみに、これから様々なD3の処理を書いていきますが、基本的に 選択・追加→属性設定
の流れになります。
また、D3はsvg.append('circle').attr('fill','none')
のように、ドット繋ぎで処理を連結させるメソッドチェーンが基本です。
なので例えばappendした後に書いた処理はappendされた要素に対して実行されますので処理の順番には注意が必要です。
svgに図形を追加
var circles1 = svg
.append("circle")
circle1
という変数において、svg
に対してbodyを追加したようにcircle
を追加します。
いきなりcircle
要素が出てきたと思われる方がいらっしゃるかもですが、これはsvg内で使用するタグになります。他にもpath
やrect
などあり、いずれも同じようにD3で制御します。
ひとまずこれでsvgの中にcircle
要素が追加されましたが、一切属性を与えておらず見えない状態なのでこれから装飾していきましょう。
図形に属性を設定
var circles1 = svg
.append("circle")
.attr('cx',100)
.attr('cy',100)
.attr('r',10)
.attr('fill','#f0f')
追加したcircle
に対してcx
とcy
を指定することでsvg内のどこに表示するか(=座標)を指定しています。
(cx
とcy
はそれぞれ円の中心座標を指定するプロパティになります)
D3で座標を指定する場合、左上がx:0,y:0になります。
なので、今回は左上から右方向に100pxのx座標、左上から下方向に100pxのy座標を円の中心にもつcircle
が描画されます。
座標は指定しましたが、円を描画するためには半径を指定する必要があります。
円の半径を指定するプロパティがr
でして、今回は10pxの半径をもつ円を描画することにしました。
最後に円をピンク色で塗りつぶすため、fill
プロパティを指定しました。
何も指定しないと黒色で塗りつぶされますが、今回は特に深い意味はありませんがピンク色にしておきました。
ポイント
D3では下記のように複数の属性をattr
で指定することになります。
hoge
.attr('cx',100)
.attr('cy',100)
.attr('r',10)
.attr('fill','#f0f')
…
v3では下記のように引数で渡せたのですが、v4からはオブジェクトを渡せなくなったのでお気をつけください。
hoge.attr({
cx: 100,
cy: 100,
r: 10,
fill: '#f0f'
})
ひとまず描画は完成
ここまでの処理をJSFiddleで走らせると、下記のような表示になります!
ひとまずはこれがD3を利用しての最低限の描画処理になります(fill
はなくてもいいですが)。
さて、今回はただsvgの中に一つの円を描画しましたが、D3を使うのであればこれを利用してデータを可視化しないと意味がないですね。
例えば、100万件あるデータを何かしらの形式のグラフにして可視化したい場合、上記のようにcircle1
みたいな変数を一つずつ作成して描画していくとしたら、担当したエンジニアは間違いなく手紙を残して突然旅に出てしまうことでしょう。
しかし、膨大なデータを配列形式でまとめてさえあげれば、そうした作業をD3が一気に処理してくれます!
というわけで早速データ可視化を試していきましょう。
データを可視化してみよう
冒頭でも触れましたが、「食戟のソーマ」という漫画が非常に面白いです。
面白すぎてこの1週間で15時間は溶かしました。
さて、この素晴らしい漫画は集英社が発刊する「週刊少年ジャンプ」にて連載されているのですが、ジャンプ内の各漫画の掲載順は人気順と言われています。
では、これほどまでに面白い「食戟のソーマ」のジャンプにおける掲載順データを折れ線グラフとして可視化してみましょう。
データを用意する
というわけで、2017年における「食戟のソーマ」の掲載順のデータが下記になります!!
var data = [
{'publishedDate': '2017-01-07','rank': 14},
{'publishedDate': '2017-01-16','rank': 13},
{'publishedDate': '2017-01-23','rank': 7},
{'publishedDate': '2017-01-30','rank': 10},
{'publishedDate': '2017-02-06','rank': 6},
{'publishedDate': '2017-02-13','rank': 6},
{'publishedDate': '2017-02-20','rank': 4},
{'publishedDate': '2017-02-27','rank': 12},
{'publishedDate': '2017-03-06','rank': 18},
{'publishedDate': '2017-03-13','rank': 11},
{'publishedDate': '2017-03-18','rank': 5},
{'publishedDate': '2017-03-27','rank': 11},
{'publishedDate': '2017-04-03','rank': 9},
{'publishedDate': '2017-04-10','rank': 6},
{'publishedDate': '2017-04-17','rank': 8},
{'publishedDate': '2017-04-27','rank': 10},
{'publishedDate': '2017-05-08','rank': 9},
{'publishedDate': '2017-05-15','rank': 12},
{'publishedDate': '2017-05-22','rank': 14},
{'publishedDate': '2017-05-29','rank': 9},
{'publishedDate': '2017-06-05','rank': 10},
{'publishedDate': '2017-06-12','rank': 5},
{'publishedDate': '2017-06-19','rank': 14},
{'publishedDate': '2017-06-26','rank': 6},
{'publishedDate': '2017-07-03','rank': 4},
{'publishedDate': '2017-07-10','rank': 14},
{'publishedDate': '2017-07-15','rank': 6},
{'publishedDate': '2017-07-24','rank': 11},
{'publishedDate': '2017-07-31','rank': 12},
{'publishedDate': '2017-08-07','rank': 5},
{'publishedDate': '2017-08-21','rank': 10},
{'publishedDate': '2017-08-28','rank': 1},
{'publishedDate': '2017-09-04','rank': 13},
{'publishedDate': '2017-09-11','rank': 14},
{'publishedDate': '2017-09-18','rank': 14},
{'publishedDate': '2017-09-25','rank': 14},
{'publishedDate': '2017-10-02','rank': 14},
{'publishedDate': '2017-10-07','rank': 9},
{'publishedDate': '2017-10-16','rank': 14},
{'publishedDate': '2017-10-23','rank': 18},
{'publishedDate': '2017-10-30','rank': 7},
{'publishedDate': '2017-11-06','rank': 15},
{'publishedDate': '2017-11-13','rank': 19}
];
このデータを集めることが、このブログを執筆する中でも最も時間がかかった作業だということは言うまでもありません。
さて、掲載順を時系列で確認したかったのでpublishedDate(発刊日)
とrank(順位)
の2つのデータをセットにして、配列に格納しています。
D3でデータを渡す際は配列形式になりますので、APIからデータを渡してもらう際などは設計に気をつけましよう。
このdata
変数を初めに宣言しておきます。
サイズ周辺の設定を変数で管理
marginの設定
var margin = {
top: 30,
right: 30,
bottom: 50,
left: 80
};
margin
というオブジェクトの中にsvg周辺の上下左右の余白を設定するための値をセットしました。
なぜわざわざこのような管理をするかというと、次に出てくるグラフのサイズ指定でmarginを差し引きするからです。
グラフのサイズを指定
var size = {
width: 1000 - margin.left - margin.right,
height: 350 - margin.top - margin.bottom
}
size
というオブジェクトにグラフの横幅・縦幅の値をセットしました。
これはsvg自体のサイズを指定しているのではなく、あくまでグラフのサイズですのでご注意ください。
実際はsvgは1000×350で描画されますが、グラフはその中でmarginを差し引いた大きさで描画されます。
svgを作成
var svg = d3.select("body")
.append("svg")
.attr("width", size.width + margin.left + margin.right)
.attr("height", size.height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
bodyに対してsvgを追加してサイズを指定するまでは先ほどの実装と同じですが、その下からはmarginを調整するための処理が追加されています。
追加したsvgの中にg
要素を追加し、translate
プロパティを用いて設定したmargin分移動させることで、左と上の余白分を作っています。
y軸のスケールを設定する
スケールの概念はD3において非常に重要なので、少し時間をかけて理解を進めていきます。
var yScale = d3.scaleLinear()
.domain([1,22])
.range([0,size.height]);
先ほどの円を描画するだけの実装では出てこなかったスケールという概念ですが、これはD3の生みの親であるマイク・ボストック氏曰く「入力ドメインを出力レンジにマップする関数」 だそうです。
例えば、次のようなデータがあるとしてそれらを今回作ったsvgの中で縦方向に描画される棒グラフで視覚化するとしましょう。
var sales = [10,100,1000,10000]
今回作ったsvgは横幅1000px・縦幅350pxで、グラフの描画領域はmarginを差し引いた分さらに狭まっています。
スケールを使わずに上記のsales
というデータを棒グラフの高さとして視覚化しようとすると、10px,100px,1000px,10000pxとなってしまいグラフの描画領域に収まらなくなってしまいます。
そこで、データの最小値・最大値、そして、描画する領域の最小座標・最大座標を指定することで、その中で収まるように縮尺を自動で調整してくれるAPIがd3.scale
になります。
- 最小値と最大値の間隔の縮尺を一定の割合で調整するのが
scaleLinear()
- 指数関数的に調整するのが
scalePow()
- 対数関数的に調整するのが
scaleLog()
だそうですが、データに大きなばらつきがない限りはscaleLinear()
で十分かと思います。
.domain([1,22])
の部分ですが、これは.domain([データの最小値,データの最大値])
という形式になっています。
今回のグラフのy軸はジャンプ内の掲載順位なので、1位から約22位までの幅の中で「食戟のソーマ」は順位を変動することになります。
つまり、y軸におけるデータの最小値と最大値をdomain()
に設定すると.domain([1,22])
となります。
※ちなみに、ほとんどのグラフのy軸は下から上に向かって値が大きくなるかと思いますが、そんな時は.domain([データの最大値,データの最小値])
のように引数の順番をひっくり返してしまえば大丈夫です
次に.range([0,size.height])
ですが、こちらも同じように.range([描画領域の最小座標,描画領域の最大座標])
という形式です。
y軸の描画領域、つまりグラフの高さはsize.height
になるため、0 ~ size.height(= 270)
という二つの座標の間の領域で描画されることになり、その設定が.range([0,size.height])
になります。
これでy軸におけるスケールの設定は全てyScale
という変数に格納され、今後y軸の指定はyScale(hoge)
という形式で指定することで、データと表示領域を加味した上で自動的にスケールした値(=座標)を得ることができます。
scaleについて
これもv3からv4の変更点で大きな箇所なので、v3ではscaleは下記のように記述していました。
d3.scale().linear()
v4になってからは名前空間の見直しの影響でキャメルになっているのでお気をつけください!
x軸のスケールを設定する
x軸も同じようにスケールを設定していきましょう!
var xScale = d3.scaleTime()
.domain([new Date(data[0].publishedDate),new Date(data[data.length - 1].publishedDate)])
.range([0,size.width]);
y軸のd3.scaleLinear()
と異なり、x軸ではd3.scaleTime()
を使用しています。
基本的にやっていることは同じなのですが、x軸は時間軸になるため、値を正確に認識・変換するためにd3.scaleTime()
を使用します。
range
に関してはy軸と同じなので割愛しますが、domain
に関しては注意が必要です。
発刊日を司るpublishedDate
は2010-01-01
のようにstrgin型で渡ってきていますので、new Date
を通してこれらをdate型に変換してから渡す必要があります。
publishedDate
の昇順でデータがソートされている前提ですが最小値はdata[0].publishedDate
で指定し、最大値はdata[data.length - 1].publishedDate)]
で一番最後のデータを取得します。
これでx軸のスケール関数の設定も完了です!
スケールの設定も完了したので早速折れ線グラフを描画したいところですが、グラフがどのような尺度(スケール)で描画されるのかを確認するためにも先にx軸・y軸それぞれの目盛りを表示してみましょう。
y軸の目盛りを表示する
svg.append("g")
.call(
d3.axisLeft(yScale)
.ticks(22)
)
.append("text")
.text("掲載順位")
.attr("stroke","#000")
.attr("x",-30)
.attr("y",0);
初めに目盛りを包括するgタグをsvg.append("g")
でsvgの中に追加します。
そして、そのgタグの中でcall()
メソッド経由してd3.axisLeft()
を実行することでgタグの中にy軸の目盛りを生成します。
axisLeft()
もv4になってからキャメルに書くようになったコンポーネントですが、名前の通り目盛りを左側に振るものです。
y軸の目盛りを作るので、引数にはyScale
を渡してあげます。
この目盛りに対してチェーンメソッドでticks()
を指定していますが、これは目盛りの数になります。
今回は全ての目盛りを表示したいと思ったので、22(全ての漫画の数)を設定しました。
最後に、y軸が何の目盛りを表しているか説明するため、gタグに.append("text")
でtextタグを追加し、attr()とtext()で装飾とテキストの設定を行いました。
ここで初めて、これまでの処理をJSFiddleで実行してみましょう!
いい感じですね、やっとグラフを実装している感じが出てきました。
x軸の目盛りを表示する
基本的にはy軸と同じなので、異なる点だけ見ていきます!
svg.append("g")
.attr("transform", "translate(0," + size.height + ")")
.call(
d3.axisTop(xScale)
.tickFormat(d3.timeFormat("%m-%d"))
)
.append("text")
.text("期間")
.attr("x",size.width)
.attr("y",margin.bottom - 10);
.attr("transform", "translate(0," + size.height + ")")
の部分ですが、追加したgタグはデフォルトの座標は[0(x),0(y)]という形式になっています。
つまり、グラフの下に表示したいのにグラフの上に表示されてしまうので、gタグのy軸の座標を"transform", "translate(0," + size.height + ")"
でグラフの高さ分指定してあげることで下にピッタリと配置できます!
.tickFormat(d3.timeFormat("%m-%d"))
は、目盛りのフォーマットを指定している部分になります。
x軸のスケールにはd3.scaleTime()
を使用しているので、フォーマットを何も指定しないとFebruary March
のように英語表記の月が振られます。
そのままでも別に良いのですが、個人的には数字表記の方が好きなのでd3.timeFormat("%m-%d")
を使用してハイフン繋ぎの表記に変更しました。
これでx軸の目盛りも完成したので、JSFiddleで表示してみましょう!
これで目盛りは完成です!!
折れ線グラフ描画関数の作成
早速折れ線グラフを描画してみたいですが、そのために下記の描画関数をまず作成しましょう。
var line = d3.line()
.x(function(d,i) { return xScale(new Date(d.publishedDate)); })
.y(function(d,i) { return yScale(d.rank); });
d3.line()
はline要素を描画するためのコンポーネントになります。
これに対してx(hoge)
でx軸の座標を、y(fuga)
でy軸の座標を渡すことで、pathのd属性
の値を生成することができます(これについては後ほど)。
x()とy()、それぞれ引数がfunction(d,i){}
という二つの引数を持つ関数形式になっていますが、d
はdata、i
はindexになります。
実はline
という変数はこの後、data
配列をループさせる時に毎回呼ばれるためd
には個別のdataが渡され、i
にはループのindex(n回目)が渡ります。
つまりこの処理は、各dataのd属性を生成するためのものです。
折れ線グラフを描画する
折れ線グラフを描画するための準備も整ったので、早速描画してみましょう!
svg.append("path")
.datum(data)
.attr("d",line);
まずsvgにpath
要素を追加した後、D3独特の書き方なのですが要素に対してデータを流し込んでいます。
データを流し込む方法はdata()
とdatum()
というものを使い分けることになるのですが、data()
は複数の要素に対してデータを流しこみ、datum()
は単一の要素に対してデータを流し込むという分け方になります。
pathは単一の属性なのでdatum()
を使い引数にdata
配列を渡すことで、内部的に各dataを回してpathのd属性を生成することができます。
pathの描画を指定したところで、早速JSFiddleで表示を確認してみましょう!!
『やばい、バグった…』と思われるかもしれませんが、大丈夫です。
本来pathはこのように線を表示しているのではなく面を表示する要素です。
なので、折れ線グラフに見せるためには黒色に塗りつぶされた箇所の外枠の線だけ表示させればいいということになります。
svg.append("path")
.datum(data)
.attr("d",line)
.attr("fill","none")
.attr("stroke","#000");
ここで大事なのはfill
という塗りつぶしを司るプロパティをnone
にすることで、黒く塗りつぶされるのを回避しています。
そして、このままでは何も表示されないのでさらに.attr("stroke","#000")
と指定することでpathのアウトラインを黒色にすることで折れ線グラフを実現できます!
遂にグラフ完成しました!
これだけでもデータ可視化はできているのですが、もう少し見やすくしたり追加機能をつけてみましょう。
折れ線グラフの頂点に印をつける
折れ線グラフは完成しましたが何か物足りないので、折れ線グラフに各頂点に丸い印でもつけてみましょう!
svg.selectAll("circle")
.data(data)
.enter()
.append('circle')
.attr("cx",line.x())
.attr("cy",line.y())
.attr("r",5)
.attr("fill","#000");
svg.selectAll("circle")
ですが、「svgの中のcircle要素を全て選択する」という処理になります。
『いや、まだcircle要素を追加してないし』と思ってしまいますが、これはまだ存在しない要素に対しても使用できます。
この後実際に追加されるcircle要素は複数存在するものなので、データはdatum(data)
ではなくdata(data)
で渡します。
今のところ全てのcircle要素にdata配列が渡る、という処理に見えますが、enter()
を実行することで配列の中身が周り.append('circle')
の部分でdata
の数だけcircle要素が生成されます。
enter()
以下の処理は配列ループの中で実行される処理みたいなイメージですね。
生成されたcircleはattrによって座標(cx,cy)・半径(r)・塗りつぶしの色(fil)を与えられて配置されます。
(line.x()
とline.y()
でそれぞれ折れ線グラフの頂点の座標にアクセスしています)
ここまでの処理を実行するとこのような表示になります。
いい感じです!
ですが、折れ線グラフの頂点の値が分かりにくいですね…食戟のソーマが何位だったのか確認したいところです。
丸印とセットで順位を表示してもいいのですが、せっかくなのでインタラクティブな動作も実装してみましょう。
頂点の丸印にホバーすると詳細データを表示
D3はただグラフを表示するだけではなく、jQueryのように各種イベントを簡単に拾うことができます。
今回はその中からホバーを試してみます!
まずはHTMLに<span class="js_toolTip"></span>
というホバー時に表示する吹き出しを追加します。
次に、cssに下記を追加します。
.js_toolTip {
display: none;
width: 2.5em;
position: absolute;
background-color: #00f;
color: #fff;
text-align: center;
}
最後に、jsの処理を追加していきます!
先ほどの折れ線グラフの頂点に丸印を追加した処理の後半に下記を追加します。
(略)
.on("mouseenter", function(d) {
d3.select('.js_toolTip').style("top", (event.pageY-30)+"px")
.style("left",(event.pageX)+"px")
.text(d.rank + "位")
.style("display", "inline-block");
})
.on("mouseout", function(d){
d3.select('.js_toolTip').style("display", "none");
});
jQueryと同じようにon('event', function(args){})
の形式でイベントをバインドできます。
今回は、丸印にホバーしたら吹き出しで順位を表示し、ホバーを外したら削除するという動きのためmouseenter
とmouseout
を使用しています。
mouseenterの処理
.js_toolTip
はcssでposition: absolute;
になっているため、top
とleft
を指定することで表示位置を制御できます。
そこで、ホバーした座標を取得するためevent.pageX
とevent.pageY
にアクセスしてホバー位置を取得します。
(event.pageY-30
と30引いているのは、ホバー位置に表示するとカーソルと被って見えづらいからです)
次に吹き出しの中身に順位を表示するために.text(d.rank + "位")
で、ホバーした要素にバインドされているデータにアクセスしてテキストを設定します。
最後に、display: none
となっている.js_toolTip
に対してdisplay: inline-block
を指定することで非表示から表示に切り替えます。
これでマウスホバーした際に吹き出しが表示されるはずです!
mouseoutの処理
カーソルが丸印から外れたら吹き出しを削除したいので、display: inline-block
となっている.js_toolTip
に対してdisplay: none
を指定することで非表示に切り替えます。
これで全ての機能が揃いました、見てみましょう!!
完成!!
まとめ
D3.js version4系のデータ可視化の基本的な実装を一行ずつ見てみましたが、いかがでしたでしょうか。
個人的には、あんなに面白い食戟のソーマが14~18位に頻繁にランクインしていることが非常にショックでした…料理漫画なのに服が弾けたりするのが原因かもしれませんね。
さて、D3ですが実際に描画するまで色々と準備が必要で初めは面倒に感じますが、目盛りなど使い回しができる処理も多いのでぜひこの機会に習得して頂ければと思います!
また、今回はほんの一部の機能しか使っていないのですが、世の中にはD3を使ってとんでもないものを作ってる方々がいらっしゃるので、サンプルを漁るもの楽しいかと思います笑
自分も引き続きこのブログでD3の機能をご案内できればと考えています!
それでは!