2012-09-07 86 views
6

我在D3.js基於Mike Bostock的節點鏈接樹創建了一棵樹。我遇到的問題以及我在Mike's Tree中看到的問題是,當沒有足夠的空間時,文本標籤會重疊/重疊圓形節點,而不是延長鏈接以留出空間。如何在D3.js中打開子元素時避免TreeMap上文本元素的重疊?

作爲一個新用戶,我不允許上傳圖片,所以這裏是一個link邁克的樹,你可以看到前面的節點標籤重疊下面的節點。

我嘗試過各種東西通過檢測文本的像素長度來解決這個問題:

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需要有一個設置高度和寬度)

回答

7

所以,下面的方法可以給不同層次的佈局提供不同的「高度」,你必須謹慎使用徑向佈局,否則你的風險不足以傳播給小圈子文字沒有重疊,但我們暫時忽略它

關鍵是要認識到樹佈局只是簡單地將事物映射到任意寬度和高度的空間,並且對角線投影將寬度(x)映射到角度和高度(y)到半徑。此外,半徑是樹的深度的簡單函數。

所以here是重新分配基於文本的長度深處方式:

首先,我用下面(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; }); 

現在我們可以像往常一樣調用佈局。還有一件事:爲了找出不同級別的半徑,我們需要先前級別半徑的累計和。一旦我們有了,我們只需將新半徑分配給計算節點。

// 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]; 
}); 

呃瞧!這可能不是最乾淨的解決方案,也許不能解決每一個問題,但它應該讓你開始。玩的開心!

+0

哇,對不起,它花了這麼長的時間來回應,但我直到現在纔看到。最後,我從來沒有最終解決這個問題,但感謝您的回答。我會看看是否可以找到舊代碼並嘗試解決問題。 – alengel