D3.js v4 の Hierarchical Edge Bundling を理解する(2)

はじめに

前回 は cluster layout で階層データを描いていました。ここから Hierarchical Edge Bundling にいく……前にこれをちょっと変えます。上下方向(水平・垂直方向)に広げて描いていた階層を円周上にレイアウトしてみたいと思います。

参考

Sample Code

サンプルコードはこれ

こういう図ができる。

変わらないところ

データの取り扱いや cluster layout で各ノードの座標を決めるところまでは同様。というか、ノードや path の描画もほぼ同じ。

変わるところ

座標系を極座標に変換している、というところがちがいます。

    var radius = Math.min(width/2, height/2);
 
    var cluster = d3.cluster().size([360, radius]);

これは、各ノードの x 座標を角度(degree)、y 座標 (root nodeからの垂直方向の距離)を半径(原点からの距離)として設定しています。

path の描画

変更しているのは d3.line() ではなく d3.radialLine() にして極座標指定で線を描いているところ。

    var line = d3.radialLine()
        .curve(d3.curveBundle.beta(0.85))
        .radius(function(d) { return d.y; })
        .angle(function(d) { return path_angle(d.x); });
    svg.selectAll("path")
        .data(links)
        .enter()
        .append("path")
        .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
                ]);
            });

function path_angle(x) {
    return rad(x);
}

とりあえず素直に前回作ったものを変換してみた。すると上の図のようになる…。root node ("Eve") の x 座標自体が角度として設定されるので、そのまま変換するとちょっと不格好な path になる。まあ、parent node 〜 child node の path にはもともと方向性があるので、そのまま変換すると偏りが出てしまうのだな。ここはもうちょっと工夫が必要。

ノードの描画

これも極座標を直交座標に変換している。90度 (Math.PI/2) ずらしているけど、これは d3.radialLine().angle() が -y (12時) のところを起点とするため。(参照 : d3/d3-shape: Graphical primitives for visualization, such as lines and areas.)

    // circle (overwrite path)
    svg.selectAll("circle")
        .data(nodes.descendants())
        .enter()
        .append("circle")
        .attrs({
            "cx" : node_x,
            "cy" : node_y,
            "r" : node_size/2
        })
        .append("title")
        .text(function(d) { return d.data.name; });

function node_angle(x) {
    return rad(x) - Math.PI/2;
}
function node_x(d) {
    return d.y * Math.cos(node_angle(d.x));
}
function node_y(d) {
    return d.y * Math.sin(node_angle(d.x));
}

描画上のトリック

    var svg = d3.select("body")
        .append("svg")
        .attrs({"width" : width,
                "height" : height})
        .append("g")
        .attr("transform", "translate(" + width/2 + "," + height/2 + ")scale(0.8,0.8)");

円形レイアウトにしたので、座標系の原点を SVG 領域の真ん中にシフトさせています(センタリング)。縮小するのは前回と同じではみだし防止。