7

Mike Bostock の Node-link Tree に基づいて、D3.js で Tree を作成しました。私が抱えていて、マイクのツリーでも見られる問題は、リンクを拡張してスペースを残すのではなく、十分なスペースがない場合に、テキストラベルが円ノードにオーバーラップ/アンダーラップすることです。

新しいユーザーとして画像をアップロードすることは許可されていません。そのため、 Mike のツリーへのリンクを次に示します。ここでは、前のノードのラベルが次のノードと重なっているのを確認できます。

テキストのピクセル長を検出することで、問題を解決するためにさまざまなことを試しました。

d3.select('.nodeText').node().getComputedTextLength();

ただし、これは、レンダリングする前に最も長いテキスト項目の長さが必要な場合に、ページをレンダリングした後にのみ機能します。

レンダリングする前に最長のテキスト項目を取得する:

nodes = tree.nodes(root).reverse();

var longest = nodes.reduce(function (a, b) { 
  return a.label.length > b.label.length ? a : b; 
});

node = vis.selectAll('g.node').data(nodes, function(d, i){
  return d.id || (d.id = ++i); 
});

nodes.forEach(function(d) {
  d.y = (longest.label.length + 200);
});

を使用している間、文字列の長さのみを返します

d.y = (d.depth * 200);

すべてのリンクを静的な長さにし、新しいノードが開いたり閉じたりしたときに美しくサイズ変更しません。

この重複を避ける方法はありますか?もしそうなら、これを行い、ツリーの動的構造を維持するための最良の方法は何でしょうか?

私が思いつくことができる解決策は 3 つありますが、それほど単純ではありません。

  1. ラベルの長さを検出し、子ノードをオーバーランする省略記号を使用します。(これにより、ラベルが読みにくくなります)
  2. ラベルの長さを検出し、それに応じてリンクを調整するように指示することで、レイアウトを動的にスケーリングします。(これが一番いいのですが、本当に難しいようです
  3. svg 要素を拡大縮小し、ラベルがはみ出し始めたらスクロール バーを使用します。(SVGには高さと幅を設定する必要があるという前提で作業しているため、これが可能かどうかはわかりません)。
4

1 に答える 1

8

したがって、次のアプローチでは、レイアウトのさまざまなレベルにさまざまな「高さ」を与えることができます。放射状のレイアウトでは、小さな円が重なり合うことなくテキストを広げるのに十分な広がりがないリスクがあることに注意する必要がありますが、今は無視しましょう。

重要なのは、ツリー レイアウトは単純に幅と高さの任意の空間にマップし、対角投影は幅 (x) を角度に、高さ (y) を半径にマップすることを理解することです。さらに、半径は木の深さの単純な関数です。

したがって、テキストの長さに基づいて深さを再割り当てする方法は次のとおりです

まず、次の (jQuery) を使用して、最大テキスト サイズを計算します。

var computeMaxTextSize = function(data, fontSize, fontName){
    var maxH = 0, maxW = 0;

    var div = document.createElement('div');
    document.body.appendChild(div);
    $(div).css({
        position: 'absolute',
        left: -1000,
        top: -1000,
        display: 'none',
        margin:0, 
        padding:0
    });

    $(div).css("font", fontSize + 'px '+fontName);

    data.forEach(function(d) {
        $(div).html(d);
        maxH = Math.max(maxH, $(div).outerHeight());
        maxW = Math.max(maxW, $(div).outerWidth());
    });

    $(div).remove();
    return {maxH: maxH, maxW: maxW};
}

ここで、レベルごとに文字列の配列を持つ配列を再帰的に構築します。

var allStrings = [[]];
var childStrings = function(level, n) {
    var a = allStrings[level];
    a.push(n.name);

    if(n.children && n.children.length > 0) {
        if(!allStrings[level+1]) {
            allStrings[level+1] = [];
        }
        n.children.forEach(function(d) {
            childStrings(level + 1, d);
        });
    }
};
childStrings(0, root);

次に、レベルごとの最大テキスト長を計算します。

var maxLevelSizes = [];
allStrings.forEach(function(d, i) {
    maxLevelSizes.push(computeMaxTextSize(allStrings[i], '10', 'sans-serif'));
});

次に、すべてのレベルの合計テキスト幅を計算します (見栄えを良くするために、小さな円のアイコンのスペースとパディングを追加します)。これが最終的なレイアウトの半径になります。この同じパディング量を後で再度使用することに注意してください。

var padding = 25; // Width of the blue circle plus some spacing
var totalRadius = d3.sum(maxLevelSizes, function(d) { return d.maxW + padding});

var diameter = totalRadius * 2; // was 960;

var tree = d3.layout.tree()
    .size([360, totalRadius])
    .separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; });

これで、通常どおりレイアウトを呼び出すことができます。最後のピースが 1 つあります。さまざまなレベルの半径を計算するには、前のレベルの半径の累積和が必要です。それができたら、計算されたノードに新しい半径を割り当てるだけです。

// Compute cummulative sums - these will be the ring radii
var newDepths = maxLevelSizes.reduce(function(prev, curr, index) {
    prev.push(prev[index] + curr.maxW + padding);                 
    return prev;
},[0]);                                                      

var nodes = tree.nodes(root);

// Assign new radius based on depth
nodes.forEach(function(d) {
    d.y = newDepths[d.depth];
});

ほら!これはおそらく最もクリーンなソリューションではなく、おそらくすべての問題に対処しているわけではありませんが、開始する必要があります. 楽しむ!

于 2012-12-13T21:44:22.947 に答える