D3.js clusterの使い方
clusterレイアウトは、決定木のようなクラスタ分析結果の表示に活用できる。データの準備やデータ構造の詳細はhierarchyの概要を参照すればよい。デモでは説明のため、もっとも単純なコードを採用している。
サンプルプログラム
サンプルコード
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>D3 hierarchy cluster</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<!-- 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>
// 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. スタイルを用意
リンクのスタイルを設定する。
<style>
.link {
fill: none;
stroke: #555;
stroke-opacity: 0.4;
stroke-width: 1.5px;
}
</style>
今回はノードのスタイルをSVG要素へ直接設定する。
2. 描画するデータを用意
描画するデータを用意する。
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);
「用意したデータ -> hierarchy用データ -> 描画種類別データ(今回はcluster)」という2段階の変換が必要である。
まず、dataをhierarchy用データ構造のルートへ変換する。
root = d3.hierarchy(data);
この変数にルートが設定される。
次に、cluster用データへ変換する関数を呼び出す。
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、深さ方向末端の座標が設定した幅になるよう計算されるため、次のようにSVGの描画領域の幅より小さくclusterの幅を設定している。
.size([height, width - 160])
4. SVG要素を設定
g = d3.select("svg").append("g").attr("transform", "translate(80,0)");
最初の要素の座標が0に設定されるため、グループを表す"g"要素を追加して全体をx方向へ移動する。このグループ要素の中にノードとリンクを設定する。
まずリンク用のSVG要素を設定する。
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;
});
データ割り当て部分で次の関数を使用している。これは入れ子になったノードを配列として並べる関数である。
root.descendants()
順序は、指定したノードに対する深さ方向、配列方向の順に表示される。この例ではA->B->C->G->H->K->D->E->F->I->Jの順である。また、子ノードから先頭に向かってリンクを描画する設定であり、先頭Aからのリンクはないため、sliceで先頭ノードを除外している。
次にノードのSVG要素を設定する。
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'の2つを設定するため、まず'g'要素を設定し、その中に'circle'と'text'を設定していく。'g'要素で位置を設定しているが、clusterの深さ方向がy、整列方向がxである点に注意する。
子ノードが多いノードの右側はリンクが密集するため、子ノードがある場合は左側、ない場合は右側にtextを設定している。"text-anchor"はtextの位置を設定するスタイルである。
まとめ
データを2回変換する必要がある点と、y座標がclusterの深さ方向である点がわかりにくい。注意が必要である。末端ノードを同じ位置に並べるのがclusterレイアウトだが、似た構造として、同じ親の子を同じ位置へ配置するtreeレイアウトがある。ほぼ同じプログラムで使い分けられる。