D3.js v4/v5 cluster 使い方 – サンプル
D3.jsの階層構造を可視化するcluster(d3.hieararchy)のプログラムデモです。決定木などのクラスター分析の結果表示に活用できます。データの準備とデータ構造については、こちらをご覧ください。デモでは解説のために最もシンプルなコードを採用しています。
プログラムデモ – 描画のみ
サンプルコード
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 82 83 84 85 |
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>D3 v5 hierarchy cluster v4/v5</title> <!-- 1. スタイルの準備 --> <style> .link { fill: none; stroke: #555; stroke-opacity: 0.4; stroke-width: 1.5px; } </style> </head> <body> <svg width="800" height="600"></svg> <script src="https://d3js.org/d3.v5.min.js"></script> <script> // 2. 描画用のデータ準備 var width = document.querySelector("svg").clientWidth; var height = document.querySelector("svg").clientHeight; var data = { "name": "A", "children": [ { "name": "B" }, { "name": "C", "children": [{ "name": "D" }, { "name": "E" }, { "name": "F" }] }, { "name": "G" }, { "name": "H", "children": [{ "name": "I" }, { "name": "J" }] }, { "name": "K" } ] }; // 3. 描画用のデータ変換 root = d3.hierarchy(data); var cluster = d3.cluster() .size([height, width - 160]) // .nodeSize([50,300]) ; // .separation(function(a, b) { return(a.parent == b.parent ? 1 : 2); }); cluster(root); // 4. svg要素の配置 g = d3.select("svg").append("g").attr("transform", "translate(80,0)"); var link = g.selectAll(".link") .data(root.descendants().slice(1)) .enter() .append("path") .attr("class", "link") .attr("d", function(d) { return "M" + d.y + "," + d.x + "C" + (d.parent.y + 100) + "," + d.x + " " + (d.parent.y + 100) + "," + d.parent.x + " " + d.parent.y + "," + d.parent.x; }); var node = g.selectAll(".node") .data(root.descendants()) .enter() .append("g") .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; }) node.append("circle") .attr("r", 8) .attr("fill", "#999"); node.append("text") .attr("dy", 3) .attr("x", function(d) { return d.children ? -12 : 12; }) .style("text-anchor", function(d) { return d.children ? "end" : "start"; }) .attr("font-size", "200%") .text(function(d) { return d.data.name; }); </script> </body> </html> |
解説
1. スタイルの準備
1 2 3 4 5 6 7 8 |
<style> .link { fill: none; stroke: #555; stroke-opacity: 0.4; stroke-width: 1.5px; } </style> |
リンクのスタイルを設定しておきます。今回、ノードのスタイルはsvg要素に直接設定します。
2. 描画用のデータ準備
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
var data = { "name": "A", "children": [ { "name": "B" }, { "name": "C", "children": [{ "name": "D" }, { "name": "E" }, { "name": "F" }] }, { "name": "G" }, { "name": "H", "children": [{ "name": "I" }, { "name": "J" }] }, { "name": "K" } ] }; |
描画用のデータを用意します。データ構造の詳細はこちらを参照ください。
3. 描画用のデータ変換
1 2 3 4 5 6 7 8 |
root = d3.hierarchy(data); var cluster = d3.cluster() .size([height, width - 160]) // .nodeSize([50,300]) ; // .separation(function(a, b) { return(a.parent == b.parent ? 1 : 2); }); cluster(root); |
準備したデータを、描画用のデータ構造に変更します。準備したデータ→hierarchy用のデータ→描画種類ごと(今回はcluster)のデータと2段階の変換が必要です。
まず、
1 |
root = d3.hierarchy(data); |
のコマンドで、dataをhierarchy用のデータ構造rootに変更します。rootには、こちらの変数が設定されます。
次に、cluster用のデータに変更するための関数を呼び出します。
1 2 3 4 5 6 |
var cluster = d3.cluster() .size([height, width - 160]) // .nodeSize([50,300]) ; // .separation(function(a, b) { return(a.parent == b.parent ? 1 : 2); }); cluster(root); |
d3.cluster()で呼び出した関数にrootを引数として設定すると、次のデータがrootに付与されます。
x | ノードの並び(兄弟)方向の座標 |
y | ノードの深さ(親子)方向の座標(先頭が0) |
今回のデモでは、付与される座標はxが画面縦方向、yが画面横方向となりますので注意が必要です。
また、d3.cluster()には以下の設定が可能です。
cluster.size() | 描画する構造のサイズを[並び方向, 深さ方向]の2要素配列で設定します。引数が設定されていない場合は現在のサイズを返します。デフォルトは[1, 1]。 |
cluster.nodeSize() | ノード1つのサイズを[並び方向, 深さ方向]の2要素配列で設定します。引数が設定されていない場合は現在のサイズを返します。デフォルトはnullです。nodeSize()がnullの場合は上のsize()を使用します。nodeSize()が指定されている場合先頭の要素の位置が(0,0)に設定されます。 |
cluster.separation() | 隣り合う要素間の間隔を決める関数を設定します。デフォルトは次の関数になっています。 function separation(a, b) { return a.parent == b.parent ? 1 : 2; } 本プログラムデモはデフォルト設定で、隣り合うノードが異なる親の場合、同じ親の場合と比較して2倍の隙間が空いています。 |
ここで、今回はsize( )を使ってclusterの幅を設定していますが、先頭の座標が0、深さ方向末端の座標が設定した幅となるように計算されるため、
1 |
.size([height, width - 160]) |
のように、svgの描画領域の幅より小さめにclusterの幅を設定しています。
4. svg要素の配置
1 |
g = d3.select("svg").append("g").attr("transform", "translate(80,0)"); |
先頭の要素の座標が0に設定されるため、グループを表す”g”要素を設定して全体をx方向に移動させます。このグループ要素の中にノードとリンクを設定していきます。
まず、リンク用のsvg要素を設定します。
1 2 3 4 5 6 7 8 9 10 11 |
var link = g.selectAll(".link") .data(root.descendants().slice(1)) .enter() .append("path") .attr("class", "link") .attr("d", function(d) { return "M" + d.y + "," + d.x + "C" + (d.parent.y + 100) + "," + d.x + " " + (d.parent.y + 100) + "," + d.parent.x + " " + d.parent.y + "," + d.parent.x; }); |
データの割り当ての部分で
1 |
root.descendants() |
の関数を使っていますが、これは入れ子になったノードを配列として並べてくれる関数です。順番は指定したノードに対する深さ方向、並び方向の順で並びます。今回のデモでは、A→B→C→G→H→K→D→E→F→I→Jの順です。また、子から先頭に向かってリンクを描画する設定で、先頭Aからのリンクはないためsliceで先頭のノードを除外しています。
続いてノードのsvg要素を設定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
var node = g.selectAll(".node") .data(root.descendants()) .enter() .append("g") .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; }) node.append("circle") .attr("r", 8) .attr("fill", "#999"); node.append("text") .attr("dy", 3) .attr("x", function(d) { return d.children ? -12 : 12; }) .style("text-anchor", function(d) { return d.children ? "end" : "start"; }) .attr("font-size", "200%") .text(function(d) { return d.data.name; }); |
ノードには’circle’と’text’の二つを設定するため、はじめに’g’要素を設定し、その中に’circle’と’text’を設定していきます。’g’要素で位置を設定していますが、clusterの深さ方向がy、並び方向がxである点に注意してください。
子ノードが多くあるノードの右側はリンクが密集するため、子ノードがある場合は右側、無い場合は左側にtextを設定しています。”text-anchor”はtextの位置を設定するスタイルです。
まとめ
データを2回変換する必要があることと、y座標がclusterの深さ方向である点が分かりにくい点です。ご注意ください。末端のノードを同じ位置に並べるのがclusterレイアウトですが、同じような構造で、同じ親の子を同じ位置に並べるtreeレイアウトがあります。ほとんど同じプログラムで使い分けることができます。