D3.js v4/v5 cluster 円周上にノードを配置する方法
D3の階層構造を表すclusterで円周上にノードを配置する方法について紹介します。階層構造(hierarchy)のデータ構造とデータの準備については、こちらをご覧ください。
プログラムデモ
サンプルコード
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 radial 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" }, { "name": "C", "children": [{ "name": "D" }, { "name": "E" }, { "name": "F" }] }, { "name": "G" }, { "name": "H", "children": [{ "name": "I" }, { "name": "J" }] }, { "name": "K" }, { "name": "L", "children": [{ "name": "M" }, { "name": "N" }] }, { "name": "O" }, { "name": "P" } ] }; var rx = width / 2; var ry = height / 2 // 2. 描画用データの変換 root = d3.hierarchy(data); var cluster = d3.cluster().size([360, ry - 80]) cluster(root); // 3. svg要素の配置 g = d3.select("svg").append("g").attr("transform", "translate(" + rx + "," + ry + ")"); var link = g.selectAll(".link") .data(root.links()) .enter() .append("path") .attr("class", "link") .attr("fill", "none") .attr("stroke", "#555") .attr("stroke-width", "1.5px") .attr("opacity", "0.6") .attr("d", d3.linkRadial() .angle(function(d) { return (d.x + 90) * Math.PI / 180; }) // .radius(function(d) { return d.y; })); var node = g.selectAll(".node") .data(root.descendants()) .enter() .append("g") .attr("transform", function(d) { return "rotate(" + (d.x) + ")translate(" + d.y + ")"; }) node.append("circle") .attr("r", 8) .attr("stroke", "steelblue") .attr("stroke-width", "1.5px") .attr("fill", "white"); node.append("text") .attr("dy", 3) .attr("dx", function(d) { return d.x < 90 || d.x > 270 ? 8 : -8; }) .style("text-anchor", function(d) { return d.x < 90 || d.x > 270 ? "start" : "end"; }) .attr("font-size", "200%") .attr("transform", function(d) { return d.x < 90 || d.x > 270 ? null : "rotate(180)"; }) .text(function(d) { return d.data.name; }); </script> </body> </html> |
解説
1. 描画用のデータ準備
描画するデータを準備します。clusterの基本的な使い方はこちら、データ構造の詳細はこちらを参照ください。
2. 描画用のデータ変換
1 2 3 |
root = d3.hierarchy(data); var cluster = d3.cluster().size([360, ry - 80]) cluster(root); |
準備したデータを、描画用のデータ構造に変更します。準備したデータ→hierarchy用のデータ→描画種類ごと(今回はcluster)のデータと2段階の変換をします。今回はx座標に回転角度、y座標に半径を設定し、極座標系の位置を計算します。
3. svg要素の配置
1 |
g = d3.select("svg").append("g").attr("transform", "translate(" + rx + "," + ry + ")"); |
はじめにsvg領域にグループを表す”g”要素を設定して、全体の中心座標を設定します。このグループ要素の中にノードとリンクを設定します。
1 2 3 4 5 6 7 8 9 10 11 12 |
var link = g.selectAll(".link") .data(root.links()) .enter() .append("path") .attr("class", "link") .attr("fill", "none") .attr("stroke", "#555") .attr("stroke-width", "1.5px") .attr("opacity", "0.6") .attr("d", d3.linkRadial() .angle(function(d) { return (d.x + 90) / 180 * Math.PI; }) .radius(function(d) { return d.y; })); |
リンクを設定します。
1 |
root.links() |
は階層構造のrootから、リンクを配列として抽出する関数です。[{“source”:nodedata,”tareget”:nodedata},…]の形の配列が定義されます。
1 2 3 |
d3.linkRadial() .angle(function(d) { return (d.x + 90) / 180 * Math.PI; }) .radius(function(d) { return d.y; }) |
は極座標系で設定されたノード間をつなぐsvg要素のpathの”d”属性の設定値を返す関数です。angleには角度を定義しますが、ラジアンである点に注意が必要です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
var node = g.selectAll(".node") .data(root.descendants()) .enter() .append("g") .attr("transform", function(d) { return "rotate(" + (d.x) + ")translate(" + d.y + ")"; }) node.append("circle") .attr("r", 8) .attr("stroke", "steelblue") .attr("stroke-width", "1.5px") .attr("fill", "white"); node.append("text") .attr("dy", 3) .attr("dx", function(d) { return d.x < 90 || d.x > 270 ? 8 : -8; }) .style("text-anchor", function(d) { return d.x < 90 || d.x > 270 ? "start" : "end"; }) .attr("font-size", "200%") .attr("transform", function(d) { return d.x < 90 || d.x > 270 ? null : "rotate(180)"; }) .text(function(d) { return d.data.name; }); |
ノードを設定します。はじめに”g”要素を設定し、その中に”circle”と”text”を設定してきます。
1 |
root.descendants() |
は、階層構造のノードを配列として取り出す関数です。また、transform属性で回転角を設定していますが、リンクの設定時と90度ずれるため注意が必要です。”text”は配置される位置によって読みやすい角度に変更します。
まとめ
ノード数が多くなるとこちらのサンプルのようなインパクトのある図が書けます。横長のトーナメント表よりもコンパクトに表示でき、デザイン性も高いです。