D3.js v4/v5 treemap 使い方 – サンプル
D3.js (v4/v5)の階層構造を可視化するtreemap(d3.treemap)のプログラムデモです。あまり見慣れないグラフですが、カテゴリーの重要度を面積で表示するものです。
サンプルデモ
サンプルプログラム
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>D3 v5 hierarchy treemap v4/v5</title> </head> <body> <svg width="800" height="600"></svg> <script src="https://d3js.org/d3.v5.min.js"></script> <script> // 1. 描画用のデータ準備 var width = document.querySelector("svg").clientWidth; var height = document.querySelector("svg").clientHeight; var data = { "name": "A", "children": [ { "name": "B", "value": 25 }, { "name": "C", "children": [ { "name": "D", "value": 10 }, { "name": "E", "value": 15 }, { "name": "F", "value": 10 } ] }, { "name": "G", "value": 15 }, { "name": "H", "children": [ { "name": "I", "value": 20 }, { "name": "J", "value": 10 } ] }, { "name": "K", "value": 10 } ] }; // 2. 描画用のデータ変換 root = d3.hierarchy(data); root .sum(function(d) { return d.value; }) .sort(function(a, b) { return b.height - a.height || b.value - a.value; }); var treemap = d3.treemap() .size([width, height]) .padding(1) .round(true); treemap(root); // 3. svg要素の配置 var g = d3.select("svg") .selectAll(".node") .data(root.leaves()) .enter() .append("g") .attr("class", "node") .attr("transform", function(d) { return "translate(" + d.x0 + "," + (d.y0) + ")"; }); g.append("rect") .style("width", function(d) { return d.x1 - d.x0; }) .style("height", function(d) { return d.y1 - d.y0; }) .style("fill", function(d) { while(d.depth > 1) d = d.parent; return d3.schemeCategory10[parseInt(d.value % 7)]; }) .style("opacity", 0.6) g.append("text") .attr("text-anchor", "start") .attr("x", 5) .attr("dy", 30) .attr("font-size", "150%") .attr("class", "node-label") .text(function(d) { return d.data.name + " : " + d.value; }); </script> </body> </html> |
解説
1. 描画用のデータ準備
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
var data = { "name": "A", "children": [ { "name": "B", "value": 25 }, { "name": "C", "children": [ { "name": "D", "value": 10 }, { "name": "E", "value": 15 }, { "name": "F", "value": 10 } ] }, { "name": "G", "value": 15 }, { "name": "H", "children": [ { "name": "I", "value": 20 }, { "name": "J", "value": 10 } ] }, { "name": "K", "value": 10 } ] }; |
描画用のデータを用意します。データ構造の詳細はこちらを参照ください。また、すべての末端ノードに、treemapの大きさを表示するためのvalue値を設定します。
2. 描画用のデータ変換
1 2 3 4 5 6 7 8 9 10 11 |
root = d3.hierarchy(data); root .sum(function(d) { return d.value; }) .sort(function(a, b) { return b.height - a.height || b.value - a.value; }); var treemap = d3.treemap() .size([width, height]) .padding(1) .round(true); treemap(root); |
準備したデータを、描画用のデータ構造に変更します。準備したデータ→hierarchy用のデータ→描画種類ごと(今回はtreemap)のデータと2段階の変換が必要です。
1 2 3 4 |
root = d3.hierarchy(data); root .sum(function(d) { return d.value; }) .sort(function(a, b) { return b.height - a.height || b.value - a.value; }); |
上記の関数でdataをhierarchy用のデータ構造rootに変更した後(データ構造詳細はこちらを参照)、value値を設定していないノードに、子孫ノードの合計value値を計算します。さらにtreemapの左上に一番合計値が大きいカテゴリーが来るようにソートしています。ソートしなければ指定したデータ構造に基づいてtreemapが作成されます。
次に、treemap用のデータに変更するための関数を呼び出します。
1 2 3 4 5 6 |
var treemap = d3.treemap() .size([width, height]) .padding(1) .round(true); treemap(root); |
d3.treemap()で呼び出した関数にrootを引数として設定すると、次のデータがrootに付与されます。
x0 | 四角形の左端座標 |
y0 | 四角形の上端座標 |
x1 | 四角形の右端座標 |
y1 | 四角形の下端座標 |
また、d3.treemap()には以下の設定が可能です。
treemap.size() | treemapレイアウト全体のサイズを[幅, 高さ]の2要素配列で設定します。引数が設定されていない場合は現在のサイズを返します。デフォルトは[1, 1]。 |
treemap.round() | 計算の丸め(四捨五入)を許容するかを指定します。trueもしくはfalseで設定します。デフォルトはfalseです。 |
treemap.padding() | 四角形の隙間の幅を設定します。値が設定されていない場合は現在の値を返します。 |
treemap.paddingInner() | 同じカテゴリー内の隙間を設定します。値が設定されていない場合は現在の値を返します。デフォルトは0です。 |
treemap.paddingOuter() | 異なるカテゴリー間の隙間を設定します。値が設定されていない場合は現在の値を返します。 |
treemap.paddingTop() | 異なるカテゴリー間の隙間(j上部)を設定します。値が設定されていない場合は現在の値を返します。デフォルトは0です。 |
treemap.paddingRight() | 上記と同じ、右側の隙間を設定します。 |
treemap.paddingBottom() | 上記と同じ、下側の隙間を設定します。 |
treemap.paddingLeft() | 上記と同じ、左側の隙間を設定します。 |
3 svg要素の配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
var g = d3.select("svg") .selectAll(".node") .data(root.leaves()) .enter() .append("g") .attr("class", "node") .attr("transform", function(d) { return "translate(" + d.x0 + "," + (d.y0) + ")"; }); g.append("rect") .style("width", function(d) { return d.x1 - d.x0; }) .style("height", function(d) { return d.y1 - d.y0; }) .style("fill", function(d) { while(d.depth > 1) d = d.parent; return d3.schemeCategory10[parseInt(d.value % 7)]; }) .style("opacity", 0.6) g.append("text") .attr("text-anchor", "start") .attr("x", 5) .attr("dy", 30) .attr("font-size", "150%") .text(function(d) { return d.data.name + " : " + d.value; }); |
svg要素に”g”タグを設定した後、その中に”rect”と”text”を設定していきます。
データの割り当て部分で
1 |
root.leaves() |
の関数を使っていますが、これは入れ子になったrootから、末尾のノードだけを取り出して配列として並べてくれる関数です。
また、四角形の背景色として、
1 2 3 4 |
.style("fill", function(d) { while(d.depth > 1) d = d.parent; return d3.schemeCategory10[parseInt(d.value % 7)]; }) |
のように色を割り当てています。まず、2行目の部分で自分が属するカテゴリーを探索します。d.depth>1として二番目の深さのカテゴリーを設定しています。d3.schemeCategory10はD3.js v5から追加された配列で0~9の値を与えると、適当な色を返してくれます。今回は2番目の深さの親のvalue値に応じて色を変更する設定にしています。v4以前であれば、下のような関数を作って呼び出してもよいです。
1 |
color = ["red", "blue", ...]; |
まとめ
このグラフはカテゴリーの重要度が面積で一目でわかり、ビックデータの可視化手法としてかなり使えます。