D3.js v4 の Hierarchical Edge Bundling を理解する(3)
Sample Code
サンプルコードはこれ
こういう図ができる。
データの準備
leaf 間のリンクデータを追加します。
var link_data = [ {"source" : "Azura", "target" : "Enoch"}, {"source" : "Enoch", "target" : "Abel"}, {"source" : "Enoch", "target" : "Noam"}, {"source" : "Azura", "target" : "Noam"}, {"source" : "Abel", "target" : "Enos"}, {"source" : "Abel", "target" : "Noam"}, {"source" : "Noam", "target" : "Enos"}, {"source" : "Enos", "target" : "Cain"} ];
変わらないところ
cluster layout を円周上に描画するところは 前回 と同じです。leaf 間のリンクだけ上書きしてます。path の書き分けのために class 指定して CSS で描き分けてるところくらいかな。
変わるところ
まず、leaf 間リンク情報を Node オブジェクトに変換します。d3.map()
については d3/d3-collection: Handy data structures for elements keyed by string. を参照。leaf 一覧 (nodes.leaves()
) を name
attribute で引っ張れるようにしています。
var nodes_name_map = d3.map(nodes.leaves(), function(d) { return d.data.name; });
leaf 間リンクのデータは name
だけ持っているので、それをキーに Node オブジェクトを取ってきます。
var interleaf_links = link_data.map(function(d) { return { "source" : nodes_name_map.get(d.source), "target" : nodes_name_map.get(d.target) }; }); console.log("interleaf_links"); console.log(interleaf_links);
leaf 間 path の描画
d3.radialLine()
はそのまま使います。ここで重要なのは node.path(target)
です。
# node.path(target) <>
Returns the shortest path through the hierarchy from this node to the specified target node. The path starts at this node, ascends to the least common ancestor of this node and the target node, and then descends to the target node. This is particularly useful for hierarchical edge bundling.
GitHub - d3/d3-hierarchy: 2D layout algorithms for visualizing hierarchical data.
path
は node と target の間に共通の parent がある場合に、そこを経由した曲線を作成してくれます。……というのもあって上の図はあえて cluster layout tree の上に上書きしてみました。この機能、d3.js v3 では d3.layout.bundle()
が提供していたようです。
// path svg.selectAll("path.interleaf") .data(interleaf_links) .enter() .append("path") .attr("class", "interleaf") .attr("d", function(d) { return line(d.source.path(d.target)); });
Hierarchical Edge Bundling
leaf のみにして parent node を描画しないようにすればできあがりです。
コードはこれ。
--- d3-hierarchical-edge-bundling.js 2017-06-03 23:56:00.309974700 +0900 +++ d3-hierarchical-edge-bundling2.js 2017-06-04 00:26:54.340704000 +0900 @@ -51,9 +51,6 @@ var node_size = 20; var cluster = d3.cluster().size([360, radius]); var nodes = cluster(root_node); - var parent2child_links = nodes.links(); - console.log("parent2child_links"); - console.log(parent2child_links); var nodes_name_map = d3.map(nodes.leaves(), function(d) { return d.data.name; }); var interleaf_links = link_data.map(function(d) { @@ -73,19 +70,6 @@ .curve(d3.curveBundle.beta(0.85)) .radius(function(d) { return d.y; }) .angle(function(d) { return path_angle(d.x); }); - svg.selectAll("path.parent2child") - .data(parent2child_links) - .enter() - .append("path") - .attr("class", "parent2child") - .attr("d", function(d) { - return line([ - d.source, - { "x" : d.source.x, "y" : (d.source.y + d.target.y)/2 }, - { "x" : d.target.x, "y" : (d.source.y + d.target.y)/2 }, - d.target - ]); - }); // path svg.selectAll("path.interleaf") @@ -99,7 +83,7 @@ // circle (overwrite path) svg.selectAll("circle") - .data(nodes.descendants()) + .data(nodes.leaves()) .enter() .append("circle") .attrs({ @@ -112,7 +96,7 @@ // text svg.selectAll("text") - .data(nodes.descendants()) + .data(nodes.leaves()) .enter() .append("text") .attrs({
おわりに
3回に分けて、順を追いつつ Hierarchical Edge Bundling の話を見てきました。D3.js Gallery にあるのは凄くきれいで洗練されてるんだけど、何をやっているのか理解したいという目的で見るとちょっと難しいね。まああれは、D3.js でどんなことができるのか、"上" のイメージを見せるものだよね。本当はこの上で、CSSを組み合わせて動的にハイライトさせるとかそういう処理があったりするんですが、そのへんは発展課題で。最小限でやるとこんな感じじゃないかなあと思っていますが、それほど javascript とかやってるわけじゃないので、もうちょっといい方法もあるかもしれません。